From 41a7064ae2b5b64e20c6d78c178d7b29e5cd6d7a Mon Sep 17 00:00:00 2001 From: Guy Luz Date: Wed, 16 Oct 2024 23:44:28 +0700 Subject: [PATCH 1/2] Created a home page when entering to the app --- assets/translations/en-US.json | 3 +- ios/Runner/AppDelegate.swift | 2 +- lib/main.dart | 2 +- lib/presentation/molecules/molecules.dart | 1 - .../molecules/page_enclosure_molecule.dart | 3 +- .../molecules/top_bar_molecule.dart | 3 - .../work_type_selection_molecule.dart | 174 ---------- .../organisms/intro/welcome_organism.dart | 70 ---- lib/presentation/organisms/organisms.dart | 3 +- .../organisms/{intro => }/tips_organism.dart | 4 +- lib/presentation/pages/activity_page.dart | 206 ++++++++++++ lib/presentation/pages/home_page.dart | 299 ++++++++---------- lib/presentation/pages/intro_page.dart | 23 +- lib/presentation/pages/pages.dart | 1 + macos/Runner/AppDelegate.swift | 2 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 17 files changed, 355 insertions(+), 445 deletions(-) delete mode 100644 lib/presentation/molecules/work_type_selection_molecule.dart delete mode 100644 lib/presentation/organisms/intro/welcome_organism.dart rename lib/presentation/organisms/{intro => }/tips_organism.dart (98%) create mode 100644 lib/presentation/pages/activity_page.dart diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index 3dc90c3..6ebdc3c 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -52,5 +52,6 @@ "break": "Take a break", "new_session": "Press to start a new session", "work_ended": "You have completed the Work session, take a break", - "break_ended": "Break ended, enter the app to continue to the next session" + "break_ended": "Break ended, enter the app to continue to the next session", + "home_page": "Home Page" } \ No newline at end of file diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 70693e4..b636303 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/lib/main.dart b/lib/main.dart index b14b6b2..8fed67c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,7 +47,7 @@ class MyApp extends StatelessWidget { localizationsDelegates: context.localizationDelegates, supportedLocales: context.supportedLocales, locale: context.locale, - home: IntroPage(), + home: HomePage(), ); } } diff --git a/lib/presentation/molecules/molecules.dart b/lib/presentation/molecules/molecules.dart index a8018a5..09cf571 100644 --- a/lib/presentation/molecules/molecules.dart +++ b/lib/presentation/molecules/molecules.dart @@ -8,5 +8,4 @@ export 'package:infinite_horizons/presentation/molecules/timer_molecule.dart'; export 'package:infinite_horizons/presentation/molecules/toggle_switch_tile_molecule.dart'; export 'package:infinite_horizons/presentation/molecules/top_bar_molecule.dart'; export 'package:infinite_horizons/presentation/molecules/web_view_molecule.dart'; -export 'package:infinite_horizons/presentation/molecules/work_type_selection_molecule.dart'; export 'package:infinite_horizons/presentation/molecules/youtube_player_molecule.dart'; diff --git a/lib/presentation/molecules/page_enclosure_molecule.dart b/lib/presentation/molecules/page_enclosure_molecule.dart index e1d319b..d1af2d1 100644 --- a/lib/presentation/molecules/page_enclosure_molecule.dart +++ b/lib/presentation/molecules/page_enclosure_molecule.dart @@ -32,6 +32,7 @@ class PageEnclosureMolecule extends StatelessWidget { Widget topBarHelper() { return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ TopBarMolecule( topBarType: topBarType, @@ -46,7 +47,7 @@ class PageEnclosureMolecule extends StatelessWidget { subTitle!, variant: TextVariant.smallTitle, ), - if (topMargin) const SeparatorAtom(variant: SeparatorVariant.farApart), + if (topMargin) const SeparatorAtom(), Expanded( child: child, ), diff --git a/lib/presentation/molecules/top_bar_molecule.dart b/lib/presentation/molecules/top_bar_molecule.dart index b0531d1..f5f7b27 100644 --- a/lib/presentation/molecules/top_bar_molecule.dart +++ b/lib/presentation/molecules/top_bar_molecule.dart @@ -44,9 +44,6 @@ class TopBarMolecule extends StatelessWidget { const SizedBox(height: 15), Row( children: [ - const Expanded( - child: SizedBox(), - ), TextAtom( title ?? '', variant: TextVariant.title, diff --git a/lib/presentation/molecules/work_type_selection_molecule.dart b/lib/presentation/molecules/work_type_selection_molecule.dart deleted file mode 100644 index d9361cc..0000000 --- a/lib/presentation/molecules/work_type_selection_molecule.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:infinite_horizons/domain/controllers/controllers.dart'; -import 'package:infinite_horizons/domain/objects/tip.dart'; -import 'package:infinite_horizons/domain/objects/work_type_abstract.dart'; -import 'package:infinite_horizons/domain/objects/work_type_analytical.dart'; -import 'package:infinite_horizons/domain/objects/work_type_creatively.dart'; -import 'package:infinite_horizons/presentation/atoms/atoms.dart'; -import 'package:infinite_horizons/presentation/molecules/molecules.dart'; - -class WorkTypeSelectionMolecule extends StatefulWidget { - const WorkTypeSelectionMolecule(this.onSelected); - - final VoidCallback onSelected; - - @override - State createState() => - _WorkTypeSelectionMoleculeState(); -} - -class _WorkTypeSelectionMoleculeState extends State { - late TipType selectedType; - bool isTextFinished = false; - late Tip analyticalTip; - late Tip creativeTip; - late List recommendedTips; - Duration? timeFromWake; - bool didPulledWakeTime = false; - late List recommendedTypeText; - - @override - void initState() { - super.initState(); - selectedType = WorkTypeAbstract.instance?.tipType ?? TipType.undefined; - initializeTips(); - } - - void onChanged(TipType? type) { - setState(() { - selectedType = type ?? TipType.undefined; - }); - WorkTypeAbstract.instance = selectedType == TipType.analytical - ? WorkTypeAnalytical() - : WorkTypeCreatively(); - - PreferencesController.instance - .setString(PreferenceKeys.tipType, selectedType.name); - - widget.onSelected(); - } - - Future initializeTips() async { - analyticalTip = tipsList.firstWhereOrNull( - (element) => element.id == 'recommended in the morning', - )!; - creativeTip = tipsList.firstWhereOrNull( - (element) => element.id == 'recommended in the evening', - )!; - - if (await HealthController.instance.isPermissionsSleepInBedGranted()) { - timeFromWake = - await HealthController.instance.getEstimatedDurationFromWake(); - } - recommendedTypeText = getRecommendedTypeText(); - setState(() { - didPulledWakeTime = true; - }); - } - - List getRecommendedTypeText() { - final DateTime now = DateTime.now(); - recommendedTips = []; - - if (creativeTip.isTipRecommendedNow(timeFromWake: timeFromWake, now: now)) { - recommendedTips.add(creativeTip); - } - - if (analyticalTip.isTipRecommendedNow( - timeFromWake: timeFromWake, - now: now, - )) { - recommendedTips.add(analyticalTip); - } - - if (recommendedTips.isEmpty) { - recommendedTips.add(creativeTip); - recommendedTips.add(analyticalTip); - } - - final String recommendedTipText = - recommendedTips.map((tip) => '${tip.actionText} Activity').join(', '); - return [ - if (recommendedTips.length == 1) - 'We recommend you to work on:' - else - 'Pick one:', - recommendedTipText, - ]; - } - - @override - Widget build(BuildContext context) { - return PageEnclosureMolecule( - scaffold: false, - title: 'work_type', - subTitle: 'Choose a work type', - child: didPulledWakeTime - ? Column( - children: [ - CardAtom( - child: Column( - children: [ - SizedBox( - width: double.infinity, - child: AnimatedTextAtom( - text: recommendedTypeText.join('\n'), - onDone: () { - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - isTextFinished = true; - }); - }); - }, - ), - ), - if (isTextFinished) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SeparatorAtom( - variant: SeparatorVariant.closeWidgets, - ), - TextAtom('* ${recommendedTips.first.reason}'), - if (recommendedTips.length > 1) - TextAtom('* ${recommendedTips[1].reason}'), - ], - ), - ], - ), - ), - if (isTextFinished) ...[ - const SeparatorAtom(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - ButtonAtom( - text: creativeTip.actionText, - icon: creativeTip.icon, - variant: ButtonVariant.mediumHighEmphasisFilledTonal, - onPressed: () { - VibrationController.instance - .vibrate(VibrationType.light); - onChanged(TipType.creative); - }, - ), - ButtonAtom( - text: analyticalTip.actionText, - icon: analyticalTip.icon, - variant: ButtonVariant.mediumHighEmphasisFilledTonal, - onPressed: () { - VibrationController.instance - .vibrate(VibrationType.light); - onChanged(TipType.analytical); - }, - ), - ], - ), - ], - ], - ) - : const CircularProgressIndicator(), - ); - } -} diff --git a/lib/presentation/organisms/intro/welcome_organism.dart b/lib/presentation/organisms/intro/welcome_organism.dart deleted file mode 100644 index a16f9fb..0000000 --- a/lib/presentation/organisms/intro/welcome_organism.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:infinite_horizons/presentation/atoms/atoms.dart'; -import 'package:infinite_horizons/presentation/core/theme_data.dart'; -import 'package:infinite_horizons/presentation/molecules/molecules.dart'; -import 'package:infinite_horizons/presentation/organisms/organisms.dart'; - -class WelcomeOrganism extends StatelessWidget { - @override - Widget build(BuildContext context) { - return PageEnclosureMolecule( - scaffold: false, - title: 'work_efficiently', - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - CardAtom( - image: Image.asset('assets/logo.png'), - imageColor: AppThemeData.logoBackgroundColor, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const TextAtom( - 'About the app', - variant: TextVariant.smallTitle, - ), - const SeparatorAtom( - variant: SeparatorVariant.relatedElements, - ), - const TextAtom( - "Sometimes we work but our focus isn't as strong as we know it could be.\nUsing this app your study and work session efficiency can increase dramatically by following methods that got approved by studies and published in research papers.\n", - ), - const SeparatorAtom(), - const TextAtom( - 'Instructions', - variant: TextVariant.smallTitle, - ), - const SeparatorAtom( - variant: SeparatorVariant.relatedElements, - ), - const TextAtom( - "Each time you sit to study a new material or work in your office we recommend opening the app and let it guide you for efficient and productive session.\n" - "The app use your responses to tailor the tips and session timer to your specific needs, so make sure to follow it as best as you can.\n\n" - "Enjoy", - ), - const SeparatorAtom(), - const SeparatorAtom(), - const TextAtom( - 'Approve all permissions for smooth experience', - ), - const SeparatorAtom(), - ButtonAtom( - variant: ButtonVariant.mediumEmphasisOutlined, - text: 'Permissions', - onPressed: () => openAlertDialog( - context, - const PermissionsOrganism(), - ), - ), - const SeparatorAtom(), - ], - ), - ), - const SeparatorAtom(), - ], - ), - ), - ); - } -} diff --git a/lib/presentation/organisms/organisms.dart b/lib/presentation/organisms/organisms.dart index 766666c..6b63e6c 100644 --- a/lib/presentation/organisms/organisms.dart +++ b/lib/presentation/organisms/organisms.dart @@ -1,6 +1,5 @@ -export 'package:infinite_horizons/presentation/organisms/intro/tips_organism.dart'; -export 'package:infinite_horizons/presentation/organisms/intro/welcome_organism.dart'; export 'package:infinite_horizons/presentation/organisms/permissions_organism.dart'; export 'package:infinite_horizons/presentation/organisms/ready_for_session_organism.dart'; export 'package:infinite_horizons/presentation/organisms/text_area_organism.dart'; export 'package:infinite_horizons/presentation/organisms/timer_organism.dart'; +export 'package:infinite_horizons/presentation/organisms/tips_organism.dart'; diff --git a/lib/presentation/organisms/intro/tips_organism.dart b/lib/presentation/organisms/tips_organism.dart similarity index 98% rename from lib/presentation/organisms/intro/tips_organism.dart rename to lib/presentation/organisms/tips_organism.dart index 9ad057a..c7d59a9 100644 --- a/lib/presentation/organisms/intro/tips_organism.dart +++ b/lib/presentation/organisms/tips_organism.dart @@ -14,7 +14,7 @@ import 'package:universal_io/io.dart'; class TipsOrganism extends StatefulWidget { const TipsOrganism(this.workType); - final String workType; + final TipType workType; @override State createState() => _TipsOrganismState(); @@ -90,7 +90,7 @@ class _TipsOrganismState extends State { return PageEnclosureMolecule( scaffold: false, - title: 'efficient_tips'.tr(args: [widget.workType.tr()]), + title: 'efficient_tips'.tr(args: [widget.workType.name.tr()]), subTitle: 'Select each element when complete', topBarTranslate: false, child: isDnd == null || !didPulledWakeTime diff --git a/lib/presentation/pages/activity_page.dart b/lib/presentation/pages/activity_page.dart new file mode 100644 index 0000000..9f7c21b --- /dev/null +++ b/lib/presentation/pages/activity_page.dart @@ -0,0 +1,206 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:infinite_horizons/domain/controllers/controllers.dart'; +import 'package:infinite_horizons/presentation/molecules/molecules.dart'; +import 'package:infinite_horizons/presentation/organisms/organisms.dart'; + +class ActivityPage extends StatefulWidget { + @override + State createState() => _ActivityPageState(); +} + +class _ActivityPageState extends State + with WidgetsBindingObserver { + int _currentTabNum = 0; + + final GlobalKey timerKey = + GlobalKey(); + + List _tabs() => [ + TimerOrganism(key: timerKey), + TextAreaOrganism(), + ]; + + final _pageController = PageController(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + PlayerController.instance.setIsSound( + PreferencesController.instance.getBool(PreferenceKeys.isSound) ?? true, + ); + PlayerController.instance.play(SoundType.startSession); + VibrationController.instance.vibrate(VibrationType.heavy); + TimerStateManager.iterateOverTimerStates(); + } + + AppLifecycleState currentAppState = AppLifecycleState.resumed; + + void setAppState(AppLifecycleState val) { + if (val == AppLifecycleState.paused || val == AppLifecycleState.resumed) { + currentAppState = val; + } + } + + @override + Future didChangeAppLifecycleState(AppLifecycleState appState) async { + switch (appState) { + case AppLifecycleState.detached: + case AppLifecycleState.inactive: + case AppLifecycleState.hidden: + setAppState(appState); + return; + case AppLifecycleState.resumed: + if (currentAppState == AppLifecycleState.resumed) { + return; + } + + setAppState(appState); + + NotificationsController.instance.cancelAllNotifications(); + + final DateTime preferencePausedTime = PreferencesController.instance + .getDateTime(PreferenceKeys.pausedTime)!; + final TimerState preferenceTimerState = TimerStateExtension.fromString( + PreferencesController.instance.getString(PreferenceKeys.timerState) ?? + '', + ); + final Duration preferenceRemainingTimerTime = PreferencesController + .instance + .getDuration(PreferenceKeys.remainingTimerTime) ?? + Duration.zero; + + setCurrentStateAndRemainingTime( + preferencePausedTime, + preferenceTimerState, + preferenceRemainingTimerTime, + ); + TimerStateManager.callback?.call(); + TimerStateManager.iterateOverTimerStates( + remainingTime: TimerStateManager.remainingTime, + ); + return; + case AppLifecycleState.paused: + setAppState(appState); + + final bool isTimerRunning = TimerStateManager.isTimerRunning(); + + TimerStateManager.pauseTimer(); + PreferencesController.instance + .setDateTime(PreferenceKeys.pausedTime, DateTime.now()); + PreferencesController.instance.setString( + PreferenceKeys.timerState, + TimerStateManager.state.name, + ); + PreferencesController.instance.setDuration( + PreferenceKeys.remainingTimerTime, + TimerStateManager.remainingTime ?? Duration.zero, + ); + + if (!isTimerRunning) { + return; + } + + final upcomingStates = TimerStateManager.upcomingStates( + TimerStateManager.state, + TimerStateManager.remainingTime, + ); + for (final UpcomingState stateWithTime in upcomingStates) { + if (stateWithTime.state != TimerState.getReadyForBreak && + stateWithTime.state != TimerState.readyToStart) { + String title; + String body; + NotificationVariant notificationVariant; + if (stateWithTime.state == TimerState.work) { + title = 'break'.tr(); + body = 'work_ended'.tr(); + notificationVariant = NotificationVariant.workEnded; + } else { + title = 'new_session'.tr(); + body = 'break_ended'.tr(); + notificationVariant = NotificationVariant.breakEnded; + } + + await NotificationsController.instance.send( + date: stateWithTime.endTime, + title: title, + body: body, + variant: notificationVariant, + ); + } + } + return; + } + } + + @override + void dispose() { + TimerStateManager.pauseTimer(); + _pageController.dispose(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + void callback(int index) { + setState(() { + _currentTabNum = index; + _pageController.animateToPage( + _currentTabNum, + duration: const Duration(milliseconds: 200), + curve: Curves.linear, + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView( + onPageChanged: (index) { + setState(() { + if (index == 0) { + FocusScope.of(context).unfocus(); + } + _currentTabNum = index; + }); + }, + controller: _pageController, + children: _tabs(), + ), + bottomNavigationBar: + BottomNavigationBarHomePage(callback, _currentTabNum), + ); + } + + /// Set the current state and the remaining time by calculating how much time passed + void setCurrentStateAndRemainingTime( + DateTime pausedTime, + TimerState previousTimerState, + Duration previousRemainingTimerTime, + ) { + final List upcomingStates = TimerStateManager.upcomingStates( + previousTimerState, + previousRemainingTimerTime, + calculateFromDate: pausedTime, + ); + final DateTime timeNow = DateTime.now(); + + UpcomingState upcomingState = upcomingStates.first; + + for (final UpcomingState tempUpcomingState in upcomingStates) { + if (tempUpcomingState.startTime().isBefore(timeNow)) { + upcomingState = tempUpcomingState; + } else { + break; + } + } + + Duration remainingDuration = Duration.zero; + if (upcomingStates.last.state != upcomingState.state) { + remainingDuration = upcomingState.endTime.difference(timeNow); + } + TimerStateManager.state = upcomingState.state; + TimerStateManager.remainingTime = remainingDuration; + } +} diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index a69af4d..970da94 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -1,205 +1,160 @@ -import 'package:easy_localization/easy_localization.dart'; +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:infinite_horizons/domain/controllers/controllers.dart'; +import 'package:infinite_horizons/domain/objects/tip.dart'; +import 'package:infinite_horizons/domain/objects/work_type_abstract.dart'; +import 'package:infinite_horizons/domain/objects/work_type_analytical.dart'; +import 'package:infinite_horizons/domain/objects/work_type_creatively.dart'; +import 'package:infinite_horizons/presentation/atoms/atoms.dart'; import 'package:infinite_horizons/presentation/molecules/molecules.dart'; -import 'package:infinite_horizons/presentation/organisms/organisms.dart'; +import 'package:infinite_horizons/presentation/pages/intro_page.dart'; class HomePage extends StatefulWidget { @override - State createState() => _HomePageState(); + State createState() => HomePageState(); } -class _HomePageState extends State with WidgetsBindingObserver { - int _currentTabNum = 0; - - final GlobalKey timerKey = - GlobalKey(); - - List _tabs() => [ - TimerOrganism(key: timerKey), - TextAreaOrganism(), - ]; - - final _pageController = PageController(); +class HomePageState extends State { + late Tip analyticalTip; + late Tip creativeTip; + late List recommendedTips; + Duration? timeFromWake; + bool didPulledWakeTime = false; + String? recommendedTypeText; @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); - PlayerController.instance.setIsSound( - PreferencesController.instance.getBool(PreferenceKeys.isSound) ?? true, - ); - PlayerController.instance.play(SoundType.startSession); - VibrationController.instance.vibrate(VibrationType.heavy); - TimerStateManager.iterateOverTimerStates(); + initializeTips(); } - AppLifecycleState currentAppState = AppLifecycleState.resumed; + void onSelectedType(TipType type) { + WorkTypeAbstract.instance = type == TipType.analytical + ? WorkTypeAnalytical() + : WorkTypeCreatively(); - void setAppState(AppLifecycleState val) { - if (val == AppLifecycleState.paused || val == AppLifecycleState.resumed) { - currentAppState = val; - } + PreferencesController.instance.setString(PreferenceKeys.tipType, type.name); + Navigator.of(context) + .push(MaterialPageRoute(builder: (context) => IntroPage(type))); } - @override - Future didChangeAppLifecycleState(AppLifecycleState appState) async { - switch (appState) { - case AppLifecycleState.detached: - case AppLifecycleState.inactive: - case AppLifecycleState.hidden: - setAppState(appState); - return; - case AppLifecycleState.resumed: - if (currentAppState == AppLifecycleState.resumed) { - return; - } - - setAppState(appState); - - NotificationsController.instance.cancelAllNotifications(); - - final DateTime preferencePausedTime = PreferencesController.instance - .getDateTime(PreferenceKeys.pausedTime)!; - final TimerState preferenceTimerState = TimerStateExtension.fromString( - PreferencesController.instance.getString(PreferenceKeys.timerState) ?? - '', - ); - final Duration preferenceRemainingTimerTime = PreferencesController - .instance - .getDuration(PreferenceKeys.remainingTimerTime) ?? - Duration.zero; - - setCurrentStateAndRemainingTime( - preferencePausedTime, - preferenceTimerState, - preferenceRemainingTimerTime, - ); - TimerStateManager.callback?.call(); - TimerStateManager.iterateOverTimerStates( - remainingTime: TimerStateManager.remainingTime, - ); - return; - case AppLifecycleState.paused: - setAppState(appState); - - final bool isTimerRunning = TimerStateManager.isTimerRunning(); + Future initializeTips() async { + analyticalTip = tipsList.firstWhereOrNull( + (element) => element.id == 'recommended in the morning', + )!; + creativeTip = tipsList.firstWhereOrNull( + (element) => element.id == 'recommended in the evening', + )!; + + if (await HealthController.instance.isPermissionsSleepInBedGranted()) { + timeFromWake = + await HealthController.instance.getEstimatedDurationFromWake(); + } + recommendedTypeText = getRecommendedTypeText(); + setState(() { + didPulledWakeTime = true; + }); + } - TimerStateManager.pauseTimer(); - PreferencesController.instance - .setDateTime(PreferenceKeys.pausedTime, DateTime.now()); - PreferencesController.instance.setString( - PreferenceKeys.timerState, - TimerStateManager.state.name, - ); - PreferencesController.instance.setDuration( - PreferenceKeys.remainingTimerTime, - TimerStateManager.remainingTime ?? Duration.zero, - ); + String? getRecommendedTypeText() { + final DateTime now = DateTime.now(); + recommendedTips = []; - if (!isTimerRunning) { - return; - } + if (creativeTip.isTipRecommendedNow(timeFromWake: timeFromWake, now: now)) { + recommendedTips.add(creativeTip); + } - final upcomingStates = TimerStateManager.upcomingStates( - TimerStateManager.state, - TimerStateManager.remainingTime, - ); - for (final UpcomingState stateWithTime in upcomingStates) { - if (stateWithTime.state != TimerState.getReadyForBreak && - stateWithTime.state != TimerState.readyToStart) { - String title; - String body; - NotificationVariant notificationVariant; - if (stateWithTime.state == TimerState.work) { - title = 'break'.tr(); - body = 'work_ended'.tr(); - notificationVariant = NotificationVariant.workEnded; - } else { - title = 'new_session'.tr(); - body = 'break_ended'.tr(); - notificationVariant = NotificationVariant.breakEnded; - } + if (analyticalTip.isTipRecommendedNow( + timeFromWake: timeFromWake, + now: now, + )) { + recommendedTips.add(analyticalTip); + } - await NotificationsController.instance.send( - date: stateWithTime.endTime, - title: title, - body: body, - variant: notificationVariant, - ); - } - } - return; + if (recommendedTips.isEmpty || recommendedTips.length >= 2) { + return null; } - } - @override - void dispose() { - TimerStateManager.pauseTimer(); - _pageController.dispose(); - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } + final String recommendedTipText = + recommendedTips.map((tip) => '${tip.actionText} Activity').join(', '); - void callback(int index) { - setState(() { - _currentTabNum = index; - _pageController.animateToPage( - _currentTabNum, - duration: const Duration(milliseconds: 200), - curve: Curves.linear, - ); - }); + return recommendedTipText; } - @override - Widget build(BuildContext context) { - return Scaffold( - body: PageView( - onPageChanged: (index) { - setState(() { - if (index == 0) { - FocusScope.of(context).unfocus(); - } - _currentTabNum = index; - }); - }, - controller: _pageController, - children: _tabs(), + Widget activityTypeCard({ + required String titleText, + required String subTitle, + required VoidCallback onClick, + }) { + return CardAtom( + // TODO: Change the image + image: Image.asset('assets/logo.png'), + child: SizedBox( + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextAtom( + titleText, + variant: TextVariant.titleLarge, + ), + TextAtom( + subTitle, + ), + const SeparatorAtom(), + ButtonAtom( + text: 'Start', + variant: ButtonVariant.mediumEmphasisOutlined, + onPressed: () { + VibrationController.instance.vibrate(VibrationType.light); + onClick(); + }, + ), + ], + ), ), - bottomNavigationBar: - BottomNavigationBarHomePage(callback, _currentTabNum), ); } - /// Set the current state and the remaining time by calculating how much time passed - void setCurrentStateAndRemainingTime( - DateTime pausedTime, - TimerState previousTimerState, - Duration previousRemainingTimerTime, - ) { - final List upcomingStates = TimerStateManager.upcomingStates( - previousTimerState, - previousRemainingTimerTime, - calculateFromDate: pausedTime, + @override + Widget build(BuildContext context) { + return PageEnclosureMolecule( + title: 'home_page', + subTitle: 'Choose an activity type to start', + expendChild: false, + child: didPulledWakeTime + ? SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (recommendedTypeText != null) ...[ + SizedBox( + width: double.infinity, + child: TextAtom( + 'We recommend you:${recommendedTypeText!}', + variant: TextVariant.smallTitle, + ), + ), + const SeparatorAtom(), + ], + // TODO: Add question mark to explain why using recommendedTips.first. + activityTypeCard( + titleText: analyticalTip.actionText, + subTitle: 'Structured processes' + '\nExamples: Engineering, Analyzing', + onClick: () => onSelectedType(TipType.analytical), + ), + const SeparatorAtom(), + activityTypeCard( + titleText: creativeTip.actionText, + subTitle: 'Abstract thinking' + '\nExamples: painting, writing fiction', + onClick: () => onSelectedType(TipType.creative), + ), + ], + ), + ) + : const CircularProgressIndicator(), ); - final DateTime timeNow = DateTime.now(); - - UpcomingState upcomingState = upcomingStates.first; - - for (final UpcomingState tempUpcomingState in upcomingStates) { - if (tempUpcomingState.startTime().isBefore(timeNow)) { - upcomingState = tempUpcomingState; - } else { - break; - } - } - - Duration remainingDuration = Duration.zero; - if (upcomingStates.last.state != upcomingState.state) { - remainingDuration = upcomingState.endTime.difference(timeNow); - } - TimerStateManager.state = upcomingState.state; - TimerStateManager.remainingTime = remainingDuration; } } diff --git a/lib/presentation/pages/intro_page.dart b/lib/presentation/pages/intro_page.dart index 112fea4..938f989 100644 --- a/lib/presentation/pages/intro_page.dart +++ b/lib/presentation/pages/intro_page.dart @@ -11,6 +11,10 @@ import 'package:infinite_horizons/presentation/pages/pages.dart'; import 'package:introduction_screen/introduction_screen.dart'; class IntroPage extends StatefulWidget { + const IntroPage(this.tipType); + + final TipType tipType; + @override State createState() => _IntroPageState(); } @@ -19,7 +23,6 @@ class _IntroPageState extends State { final GlobalKey _introKey = GlobalKey(); - String workType = ''; bool showNextButton = true; IntroState state = IntroState.welcome; final Duration selectionTransitionDelay = const Duration(milliseconds: 200); @@ -48,7 +51,7 @@ class _IntroPageState extends State { } void onDone(BuildContext context) => Navigator.of(context) - .push(MaterialPageRoute(builder: (context) => HomePage())); + .push(MaterialPageRoute(builder: (context) => ActivityPage())); void previousPage() => _introKey.currentState?.previous(); @@ -87,7 +90,7 @@ class _IntroPageState extends State { return Scaffold( body: PopScope( canPop: state == IntroState.welcome, - onPopInvoked: (_) => previousPage(), + onPopInvokedWithResult: (bool a, b) => previousPage(), child: GestureDetector( onHorizontalDragEnd: onHorizontalDrag, child: IntroductionScreen( @@ -116,19 +119,7 @@ class _IntroPageState extends State { ), pages: [ customPageViewModel( - bodyWidget: WelcomeOrganism(), - ), - customPageViewModel( - bodyWidget: WorkTypeSelectionMolecule(() async { - setState(() { - workType = WorkTypeAbstract.instance!.tipType.name; - }); - await Future.delayed(selectionTransitionDelay); - nextPage(); - }), - ), - customPageViewModel( - bodyWidget: TipsOrganism(workType), + bodyWidget: TipsOrganism(widget.tipType), ), customPageViewModel( bodyWidget: EnergySelectionMolecule(() async { diff --git a/lib/presentation/pages/pages.dart b/lib/presentation/pages/pages.dart index ee57df5..3e69fd5 100644 --- a/lib/presentation/pages/pages.dart +++ b/lib/presentation/pages/pages.dart @@ -1,3 +1,4 @@ +export 'package:infinite_horizons/presentation/pages/activity_page.dart'; export 'package:infinite_horizons/presentation/pages/energy_tips_page.dart'; export 'package:infinite_horizons/presentation/pages/home_page.dart'; export 'package:infinite_horizons/presentation/pages/intro_page.dart'; diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift index d53ef64..8e02df2 100644 --- a/macos/Runner/AppDelegate.swift +++ b/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index e6e13da..bfcd609 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -19,6 +20,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("AwesomeNotificationsPluginCApi")); AwesomeNotificationsCorePluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("AwesomeNotificationsCorePluginCApi")); + FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4a0ea9c..6497f48 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST audioplayers_windows awesome_notifications awesome_notifications_core + flutter_inappwebview_windows permission_handler_windows url_launcher_windows ) From 40a37a88eb99632ff653c1980e67235fc2e97dd1 Mon Sep 17 00:00:00 2001 From: Guy Luz Date: Thu, 17 Oct 2024 20:30:53 +0700 Subject: [PATCH 2/2] Added images and recommended activity explanation --- assets/images/brain.svg | 1760 +++++++++++++++++++++++++ assets/images/light_bulb.svg | 401 ++++++ lib/presentation/atoms/card_atom.dart | 45 +- lib/presentation/pages/home_page.dart | 49 +- pubspec.yaml | 2 + 5 files changed, 2229 insertions(+), 28 deletions(-) create mode 100644 assets/images/brain.svg create mode 100644 assets/images/light_bulb.svg diff --git a/assets/images/brain.svg b/assets/images/brain.svg new file mode 100644 index 0000000..b675c14 --- /dev/null +++ b/assets/images/brain.svg @@ -0,0 +1,1760 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/light_bulb.svg b/assets/images/light_bulb.svg new file mode 100644 index 0000000..90fc80e --- /dev/null +++ b/assets/images/light_bulb.svg @@ -0,0 +1,401 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/lib/presentation/atoms/card_atom.dart b/lib/presentation/atoms/card_atom.dart index 1492aeb..8038b0f 100644 --- a/lib/presentation/atoms/card_atom.dart +++ b/lib/presentation/atoms/card_atom.dart @@ -1,32 +1,39 @@ import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:infinite_horizons/presentation/core/theme_data.dart'; class CardAtom extends StatelessWidget { - const CardAtom({required this.child, this.image, this.imageColor}); + const CardAtom({required this.child, this.onClick, this.image}); final Widget child; - final Image? image; - final Color? imageColor; + final SvgPicture? image; + final VoidCallback? onClick; @override Widget build(BuildContext context) { - return Card.filled( - margin: EdgeInsets.zero, - clipBehavior: Clip.hardEdge, - child: Column( - children: [ - if (image != null) - Container( - height: 250, - width: double.infinity, - color: imageColor, - child: image, + return GestureDetector( + onTap: onClick, + child: Card.filled( + margin: EdgeInsets.zero, + clipBehavior: Clip.hardEdge, + child: Column( + children: [ + if (image != null) + ClipRRect( + borderRadius: + const BorderRadius.vertical(bottom: Radius.circular(15)), + child: SizedBox( + height: 170, + width: double.infinity, + child: image, + ), + ), + Padding( + padding: const EdgeInsets.all(AppThemeData.generalSpacing), + child: child, ), - Padding( - padding: const EdgeInsets.all(AppThemeData.generalSpacing), - child: child, - ), - ], + ], + ), ), ); } diff --git a/lib/presentation/pages/home_page.dart b/lib/presentation/pages/home_page.dart index 970da94..b96a6b5 100644 --- a/lib/presentation/pages/home_page.dart +++ b/lib/presentation/pages/home_page.dart @@ -1,5 +1,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:infinite_horizons/domain/controllers/controllers.dart'; import 'package:infinite_horizons/domain/objects/tip.dart'; import 'package:infinite_horizons/domain/objects/work_type_abstract.dart'; @@ -85,10 +87,11 @@ class HomePageState extends State { required String titleText, required String subTitle, required VoidCallback onClick, + required SvgPicture background, }) { return CardAtom( - // TODO: Change the image - image: Image.asset('assets/logo.png'), + image: background, + onClick: onClick, child: SizedBox( width: double.infinity, child: Column( @@ -128,20 +131,43 @@ class HomePageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ if (recommendedTypeText != null) ...[ - SizedBox( - width: double.infinity, - child: TextAtom( - 'We recommend you:${recommendedTypeText!}', - variant: TextVariant.smallTitle, - ), + Row( + children: [ + TextAtom( + 'We recommend you: ${recommendedTypeText!}', + variant: TextVariant.smallTitle, + ), + const Expanded(child: Text('')), + IconButton( + onPressed: () { + openAlertDialog( + context, + SizedBox( + height: 150, + child: PageEnclosureMolecule( + title: 'Recommended type', + subTitle: recommendedTips.first.actionText, + expendChild: false, + child: + TextAtom(recommendedTips.first.reason!), + ), + ), + ); + }, + icon: const FaIcon(FontAwesomeIcons.circleQuestion), + ), + ], ), const SeparatorAtom(), ], - // TODO: Add question mark to explain why using recommendedTips.first. activityTypeCard( titleText: analyticalTip.actionText, subTitle: 'Structured processes' '\nExamples: Engineering, Analyzing', + background: SvgPicture.asset( + 'assets/images/brain.svg', + fit: BoxFit.cover, + ), onClick: () => onSelectedType(TipType.analytical), ), const SeparatorAtom(), @@ -149,8 +175,13 @@ class HomePageState extends State { titleText: creativeTip.actionText, subTitle: 'Abstract thinking' '\nExamples: painting, writing fiction', + background: SvgPicture.asset( + 'assets/images/light_bulb.svg', + fit: BoxFit.cover, + ), onClick: () => onSelectedType(TipType.creative), ), + const SeparatorAtom(variant: SeparatorVariant.farApart), ], ), ) diff --git a/pubspec.yaml b/pubspec.yaml index bc2ddf6..f8293bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: flutter_cached_pdfview: ^0.4.2 flutter_dnd: ^0.1.4+1 flutter_inappwebview: ^6.0.0 + flutter_svg: ^2.0.10+1 flutter_vibrate: ^1.3.0 # Font Awesome Icon pack available as Flutter Icons. font_awesome_flutter: ^10.7.0 @@ -65,6 +66,7 @@ flutter: - assets/ - assets/translations/ - assets/sound_effects/ + - assets/images/ flutter_launcher_icons: