-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Improve onboarding flow with guided navigation and microphone button #4033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:flutter/services.dart'; | ||||||||
| import 'package:provider/provider.dart'; | ||||||||
| import 'widgets/action_item_form_sheet.dart'; | ||||||||
| import 'widgets/tasks_onboarding_widget.dart'; | ||||||||
|
|
||||||||
| import 'package:omi/backend/preferences.dart'; | ||||||||
| import 'package:omi/backend/schema/schema.dart'; | ||||||||
| import 'package:omi/providers/action_items_provider.dart'; | ||||||||
| import 'package:omi/utils/analytics/mixpanel.dart'; | ||||||||
|
|
@@ -38,16 +40,36 @@ class _ActionItemsPageState extends State<ActionItemsPage> with AutomaticKeepAli | |||||||
| void initState() { | ||||||||
| super.initState(); | ||||||||
| _scrollController.addListener(_onScroll); | ||||||||
| WidgetsBinding.instance.addPostFrameCallback((_) { | ||||||||
| WidgetsBinding.instance.addPostFrameCallback((_) async { | ||||||||
| if (!mounted) return; | ||||||||
| MixpanelManager().actionItemsPageOpened(); | ||||||||
| final provider = Provider.of<ActionItemsProvider>(context, listen: false); | ||||||||
| if (provider.actionItems.isEmpty) { | ||||||||
| provider.fetchActionItems(showShimmer: true); | ||||||||
| await provider.fetchActionItems(showShimmer: true); | ||||||||
| } | ||||||||
|
|
||||||||
| // Create onboarding task if needed | ||||||||
| if (!SharedPreferencesUtil().isHomeOnboardingCompleted) { | ||||||||
| _createOnboardingTaskIfNeeded(provider); | ||||||||
| } | ||||||||
| }); | ||||||||
| } | ||||||||
|
|
||||||||
| void _createOnboardingTaskIfNeeded(ActionItemsProvider provider) { | ||||||||
| // Check if "Open your brain map" task already exists | ||||||||
| const onboardingTaskDescription = 'Open your brain map'; | ||||||||
| final existingTask = provider.actionItems.any( | ||||||||
| (item) => item.description.toLowerCase() == onboardingTaskDescription.toLowerCase(), | ||||||||
| ); | ||||||||
|
|
||||||||
| if (!existingTask) { | ||||||||
| provider.createActionItem( | ||||||||
| description: onboardingTaskDescription, | ||||||||
| dueAt: null, // No deadline | ||||||||
| ); | ||||||||
| } | ||||||||
| } | ||||||||
|
|
||||||||
| @override | ||||||||
| void dispose() { | ||||||||
| _scrollController.removeListener(_onScroll); | ||||||||
|
|
@@ -373,6 +395,10 @@ class _ActionItemsPageState extends State<ActionItemsPage> with AutomaticKeepAli | |||||||
| ), | ||||||||
| ), | ||||||||
|
|
||||||||
| // Tasks onboarding widget | ||||||||
| if (!SharedPreferencesUtil().isHomeOnboardingCompleted) | ||||||||
| const SliverToBoxAdapter(child: TasksOnboardingWidget()), | ||||||||
|
Comment on lines
+399
to
+400
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The visibility of
Suggested change
|
||||||||
|
|
||||||||
| // Bottom padding | ||||||||
| const SliverPadding(padding: EdgeInsets.only(bottom: 100)), | ||||||||
| ], | ||||||||
|
|
||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:omi/backend/preferences.dart'; | ||
|
|
||
| class TasksOnboardingWidget extends StatelessWidget { | ||
| const TasksOnboardingWidget({super.key}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| if (SharedPreferencesUtil().isHomeOnboardingCompleted) { | ||
| return const SizedBox.shrink(); | ||
| } | ||
|
|
||
| return Padding( | ||
| padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| "Your tasks are created\nautomatically\nfrom conversations and chat", | ||
| style: TextStyle( | ||
| color: Colors.grey.shade400, | ||
| fontSize: 16, | ||
| fontWeight: FontWeight.w400, | ||
| height: 1.3, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 12), | ||
| // Vertical line | ||
| Padding( | ||
| padding: const EdgeInsets.only(left: 40), | ||
| child: Container( | ||
| width: 2, | ||
| height: 40, | ||
| color: Colors.grey.shade600, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 12), | ||
| Text( | ||
| "Now let's check your memories", | ||
| style: TextStyle( | ||
| color: Colors.grey.shade400, | ||
| fontSize: 16, | ||
| fontWeight: FontWeight.w400, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 8), | ||
| // Arrow pointing down to memories tab (3rd icon, right of center) | ||
| Padding( | ||
| padding: const EdgeInsets.only(left: 220), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a hardcoded padding value |
||
| child: CustomPaint( | ||
| size: const Size(40, 70), | ||
| painter: _ArrowDownPainter(), | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Arrow pointing straight down | ||
| class _ArrowDownPainter extends CustomPainter { | ||
| @override | ||
| void paint(Canvas canvas, Size size) { | ||
| final paint = Paint() | ||
| ..color = Colors.grey.shade600 | ||
| ..strokeWidth = 2 | ||
| ..style = PaintingStyle.stroke | ||
| ..strokeCap = StrokeCap.round; | ||
|
|
||
| // Curved line going down | ||
| final path = Path(); | ||
| path.moveTo(size.width * 0.5, 0); | ||
| path.quadraticBezierTo( | ||
| size.width * 0.6, | ||
| size.height * 0.5, | ||
| size.width * 0.5, | ||
| size.height * 0.85, | ||
| ); | ||
|
|
||
| canvas.drawPath(path, paint); | ||
|
|
||
| // Arrow head pointing down | ||
| final arrowPath = Path(); | ||
| arrowPath.moveTo(size.width * 0.5 - 6, size.height * 0.72); | ||
| arrowPath.lineTo(size.width * 0.5, size.height * 0.85); | ||
| arrowPath.lineTo(size.width * 0.5 + 6, size.height * 0.72); | ||
|
|
||
| canvas.drawPath(arrowPath, paint); | ||
| } | ||
|
|
||
| @override | ||
| bool shouldRepaint(covariant CustomPainter oldDelegate) => false; | ||
| } | ||
|
Comment on lines
+62
to
+94
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:omi/backend/preferences.dart'; | ||
|
|
||
| class HomeOnboardingWidget extends StatelessWidget { | ||
| const HomeOnboardingWidget({super.key}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| if (SharedPreferencesUtil().isHomeOnboardingCompleted) { | ||
| return const SizedBox.shrink(); | ||
| } | ||
|
|
||
| return Padding( | ||
| padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), | ||
| child: Column( | ||
| crossAxisAlignment: CrossAxisAlignment.start, | ||
| children: [ | ||
| Text( | ||
| "Here you'll see your\nconversations", | ||
| style: TextStyle( | ||
| color: Colors.grey.shade400, | ||
| fontSize: 18, | ||
| fontWeight: FontWeight.w400, | ||
| height: 1.3, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 12), | ||
| // Vertical line | ||
| Padding( | ||
| padding: const EdgeInsets.only(left: 40), | ||
| child: Container( | ||
| width: 2, | ||
| height: 40, | ||
| color: Colors.grey.shade600, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 12), | ||
| Text( | ||
| "Let's check your tasks", | ||
| style: TextStyle( | ||
| color: Colors.grey.shade400, | ||
| fontSize: 18, | ||
| fontWeight: FontWeight.w400, | ||
| ), | ||
| ), | ||
| const SizedBox(height: 8), | ||
| // Arrow pointing down to tasks tab (2nd icon) | ||
| Padding( | ||
| padding: const EdgeInsets.only(left: 85), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a hardcoded padding value |
||
| child: CustomPaint( | ||
| size: const Size(40, 70), | ||
| painter: _ArrowDownPainter(), | ||
| ), | ||
| ), | ||
| ], | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| // Arrow pointing straight down | ||
| class _ArrowDownPainter extends CustomPainter { | ||
| @override | ||
| void paint(Canvas canvas, Size size) { | ||
| final paint = Paint() | ||
| ..color = Colors.grey.shade600 | ||
| ..strokeWidth = 2 | ||
| ..style = PaintingStyle.stroke | ||
| ..strokeCap = StrokeCap.round; | ||
|
|
||
| // Curved line going down | ||
| final path = Path(); | ||
| path.moveTo(size.width * 0.5, 0); | ||
| path.quadraticBezierTo( | ||
| size.width * 0.6, | ||
| size.height * 0.5, | ||
| size.width * 0.5, | ||
| size.height * 0.85, | ||
| ); | ||
|
|
||
| canvas.drawPath(path, paint); | ||
|
|
||
| // Arrow head pointing down | ||
| final arrowPath = Path(); | ||
| arrowPath.moveTo(size.width * 0.5 - 6, size.height * 0.72); | ||
| arrowPath.lineTo(size.width * 0.5, size.height * 0.85); | ||
| arrowPath.lineTo(size.width * 0.5 + 6, size.height * 0.72); | ||
|
|
||
| canvas.drawPath(arrowPath, paint); | ||
| } | ||
|
|
||
| @override | ||
| bool shouldRepaint(covariant CustomPainter oldDelegate) => false; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The value
10used as the view count threshold is a magic number. It should be extracted into a named constant to improve readability and maintainability.References