diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 3cbabd7..bf0c95e 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -58,6 +58,9 @@ + + + diff --git a/.idea/runConfigurations/development.xml b/.idea/runConfigurations/development.xml new file mode 100644 index 0000000..07b02c6 --- /dev/null +++ b/.idea/runConfigurations/development.xml @@ -0,0 +1,7 @@ + + + + diff --git a/.idea/runConfigurations/production.xml b/.idea/runConfigurations/production.xml new file mode 100644 index 0000000..1c5c774 --- /dev/null +++ b/.idea/runConfigurations/production.xml @@ -0,0 +1,7 @@ + + + + diff --git a/.idea/runConfigurations/staging.xml b/.idea/runConfigurations/staging.xml new file mode 100644 index 0000000..f979a68 --- /dev/null +++ b/.idea/runConfigurations/staging.xml @@ -0,0 +1,7 @@ + + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..4002c20 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,32 @@ +## How to Contribute + +1. **Read The Branch naming convention** + > How to name a branch + +2. **Fork and Clone the Project** + ```bash + git clone https://github.com/JordyHers-org/Times-up-flutter.git + cd Times-up-flutter/ + ``` + +3. **Install Flutter Version** + > Install FVM via Homebrew and use Flutter version 3.7.12. + ```bash + + brew install fvm + fvm install 3.7.12 + ``` + +4. **Request Firebase Options File** + > Request the Firebase options file from the Project Owner and place it in the appropriate location. From discord server +Jordyhers [Discord- JordyHers](https://discord.gg/e4ppDx9Zcy) + +5. **Extra** + > For child's pictures feel free to use any of the pictures available. + +| | | | +|-|-|-| +| Neymar | Momo | Gareth +| Titi | Bruyne | Kylian +| Leo | Sally | Harry | + diff --git a/README.md b/README.md index 658406e..dec0439 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ settled for user, and doesn't collect data for third parties companies. | | | | |-|-|-| | Onboarding | Sign_in_page | child_list_page -| ChildDetailsPage | ChildNotificationRemoval | GuidedTour -| NotificationSending | ChildLocation | settings_page | +| ChildDetailsPage | ChildNotificationRemoval | GuidedTour +| NotificationSending | ChildLocation | settings_page | ## Application Feature: Child Side diff --git a/android/app/build.gradle b/android/app/build.gradle index e224b11..9408c2d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -36,7 +36,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 33 + compileSdkVersion 34 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2070d06..f9e0506 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,15 +7,15 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> - - - + + + + + + + createState() => _TimesUpAppState(); -} - -class _TimesUpAppState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { SystemChrome.setPreferredOrientations([ @@ -24,6 +20,7 @@ class _TimesUpAppState extends State with WidgetsBindingObserver { return MaterialApp( debugShowCheckedModeBanner: false, + locale: Provider.of(context).locale, title: Strings.appName, theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, diff --git a/lib/sign_in/phone_sign_bloc_based.dart b/lib/app/config/android_config.dart similarity index 100% rename from lib/sign_in/phone_sign_bloc_based.dart rename to lib/app/config/android_config.dart diff --git a/lib/app/bloc/child_side_bloc.dart b/lib/app/features/child_side/bloc/child_side_bloc.dart similarity index 100% rename from lib/app/bloc/child_side_bloc.dart rename to lib/app/features/child_side/bloc/child_side_bloc.dart diff --git a/lib/app/bloc/child_side_event.dart b/lib/app/features/child_side/bloc/child_side_event.dart similarity index 100% rename from lib/app/bloc/child_side_event.dart rename to lib/app/features/child_side/bloc/child_side_event.dart diff --git a/lib/app/bloc/child_side_state.dart b/lib/app/features/child_side/bloc/child_side_state.dart similarity index 100% rename from lib/app/bloc/child_side_state.dart rename to lib/app/features/child_side/bloc/child_side_state.dart diff --git a/lib/app/pages/child_page.dart b/lib/app/features/child_side/child_page.dart similarity index 91% rename from lib/app/pages/child_page.dart rename to lib/app/features/child_side/child_page.dart index ccd7642..3ce20db 100644 --- a/lib/app/pages/child_page.dart +++ b/lib/app/features/child_side/child_page.dart @@ -7,18 +7,19 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/app/bloc/child_side_bloc.dart'; +import 'package:times_up_flutter/app/features/child_side/bloc/child_side_bloc.dart'; +import 'package:times_up_flutter/app/features/child_side/set_child_page.dart'; +import 'package:times_up_flutter/app/features/landing_page.dart'; import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; -import 'package:times_up_flutter/app/landing_page.dart'; -import 'package:times_up_flutter/app/pages/set_child_page.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_empty_content.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/models/notification_model/notification_model.dart'; import 'package:times_up_flutter/services/app_usage_service.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_empty_content.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; class ChildPage extends StatefulWidget { const ChildPage({ @@ -66,11 +67,23 @@ class _ChildPageState extends State with WidgetsBindingObserver { Navigator.pop(context); } + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override void didChangeAppLifecycleState(AppLifecycleState state) { - Timer.periodic(const Duration(minutes: 5), (timer) { - widget.database - ?.liveUpdateChild(widget.child!, timer.tick, widget.appUsage); + Timer.periodic(const Duration(seconds: 5), (timer) async { + await widget.database?.liveUpdateChild(widget.child!, widget.appUsage); + JHLogger.$.d('${timer.tick} - $state'); }); super.didChangeAppLifecycleState(state); } @@ -202,7 +215,7 @@ class _ChildPageState extends State with WidgetsBindingObserver { } else if (state is ChildSideFetching) { return _buildLoading(); } else if (state is ChildSideNotification) { - return _buildNotification(); + return _buildNotification(widget.child!); } else if (state is ChildSideAppList) { return _buildAppList(widget.appUsage); } else { @@ -236,9 +249,9 @@ class _ChildPageState extends State with WidgetsBindingObserver { ); } - Widget _buildNotification() { + Widget _buildNotification(ChildModel child) { return StreamBuilder>( - stream: widget.database!.notificationStream(childId: ''), + stream: widget.database!.notificationStream(childId: child.id), builder: (BuildContext context, snapshot) { if (snapshot.hasData) { final data = snapshot.data; diff --git a/lib/app/pages/set_child_page.dart b/lib/app/features/child_side/set_child_page.dart similarity index 89% rename from lib/app/pages/set_child_page.dart rename to lib/app/features/child_side/set_child_page.dart index a6f334f..19de91a 100644 --- a/lib/app/pages/set_child_page.dart +++ b/lib/app/features/child_side/set_child_page.dart @@ -4,16 +4,16 @@ import 'package:battery_plus/battery_plus.dart'; import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:times_up_flutter/app/features/child_side/child_page.dart'; import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; -import 'package:times_up_flutter/app/pages/child_page.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_form_submit_button.dart'; -import 'package:times_up_flutter/common_widgets/show_alert_dialog.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/services/app_usage_service.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_form_submit_button.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; enum AppState { loading, complete } @@ -56,26 +56,27 @@ class _SetChildPageState extends State { } Future _submit(String name, String key, BuildContext context) async { - final database = Provider.of(context, listen: false); + final databaseStore = Provider.of(context, listen: false); final geo = Provider.of(context, listen: false); - final apps = Provider.of(context, listen: false); + final appUsage = Provider.of(context, listen: false); final position = await geo.getInitialLocation(); final battery = await Battery().batteryLevel; + try { - final response = await database.getUserCurrentChild( + final response = await databaseStore.getUserCurrentChild( key, - apps, + appUsage, GeoPoint(position.latitude, position.longitude), battery: battery.toString(), ); - JHLogger.$.d('RESPONSE : $response'); + try { if (mounted) { await Navigator.of(context).pushReplacement( MaterialPageRoute( fullscreenDialog: true, builder: (context) => - ChildPage.create(context, database, response), + ChildPage.create(context, databaseStore, response), ), ); } diff --git a/lib/app/landing_page.dart b/lib/app/features/landing_page.dart similarity index 90% rename from lib/app/landing_page.dart rename to lib/app/features/landing_page.dart index 02be44e..dc5fe72 100644 --- a/lib/app/landing_page.dart +++ b/lib/app/features/landing_page.dart @@ -2,15 +2,15 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:showcaseview/showcaseview.dart'; -import 'package:times_up_flutter/app/pages/parent_page.dart'; -import 'package:times_up_flutter/app/pages/set_child_page.dart'; -import 'package:times_up_flutter/common_widgets/jh_loading_widget.dart'; +import 'package:times_up_flutter/app/features/child_side/set_child_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/parent_page.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_page.dart'; import 'package:times_up_flutter/services/app_usage_service.dart'; import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; import 'package:times_up_flutter/services/shared_preferences.dart'; -import 'package:times_up_flutter/sign_in/sign_in_page.dart'; +import 'package:times_up_flutter/widgets/jh_loading_widget.dart'; enum AppSide { parent, child } @@ -32,7 +32,7 @@ class _LandingPageState extends State { } Future _setFlagParentOrChild() async { - final isParent = await SharedPreference().getParentOrChild(); + final isParent = await CacheService.getParentOrChild(); setState(() { isParent ? _side = AppSide.parent : _side = AppSide.child; }); diff --git a/lib/app/features/parent_side/app_list_page.dart b/lib/app/features/parent_side/app_list_page.dart new file mode 100644 index 0000000..bd91364 --- /dev/null +++ b/lib/app/features/parent_side/app_list_page.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; +import 'package:times_up_flutter/models/child_model/child_model.dart'; + +class AppListPage extends StatelessWidget { + const AppListPage({ + required this.childModel, + Key? key, + }) : super(key: key); + + final ChildModel childModel; + + static Future show(BuildContext context, ChildModel model) async { + await Navigator.of(context).push( + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) { + return AppListPage(childModel: model); + }, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1, 0); + const end = Offset.zero; + const curve = Curves.easeInOut; + final tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + final offsetAnimation = animation.drive(tween); + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + transitionDuration: const Duration(milliseconds: 400), + ), + ); + } + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + + return Scaffold( + appBar: AppBar( + leading: IconButton( + icon: const Icon(Icons.chevron_left), + onPressed: () => Navigator.of(context).pop(), + ), + elevation: 0, + backgroundColor: themeData.scaffoldBackgroundColor, + ), + body: ListView.builder( + physics: const BouncingScrollPhysics( + decelerationRate: ScrollDecelerationRate.fast, + ), + itemCount: childModel.appsUsageModel.length, + itemBuilder: (context, index) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: childModel.appsUsageModel[index].appIcon != null + ? Image.memory( + childModel.appsUsageModel[index].appIcon!, + height: 35, + ) + : const Icon(Icons.android), + title: Text( + childModel.appsUsageModel[index].appName, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: themeData.dividerColor, + ), + ), + trailing: Text( + childModel.appsUsageModel[index].usage.toString().t(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: themeData.dividerColor, + ), + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/app/pages/child_details_page.dart b/lib/app/features/parent_side/child_details_page.dart similarity index 58% rename from lib/app/pages/child_details_page.dart rename to lib/app/features/parent_side/child_details_page.dart index d5bfb94..21340a6 100644 --- a/lib/app/pages/child_details_page.dart +++ b/lib/app/features/parent_side/child_details_page.dart @@ -9,23 +9,23 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:times_up_flutter/app/features/parent_side/app_list_page.dart'; import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; -import 'package:times_up_flutter/common_widgets/jh_bar_chart.dart'; -import 'package:times_up_flutter/common_widgets/jh_battery_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_empty_content.dart'; -import 'package:times_up_flutter/common_widgets/jh_feature_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_header_widget.dart'; -import 'package:times_up_flutter/common_widgets/show_alert_dialog.dart'; -import 'package:times_up_flutter/common_widgets/show_bottom_sheet.dart'; -import 'package:times_up_flutter/common_widgets/show_exeption_alert.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/l10n/l10n.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/models/notification_model/notification_model.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_battery_widget.dart'; +import 'package:times_up_flutter/widgets/jh_custom_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_empty_content.dart'; +import 'package:times_up_flutter/widgets/jh_header_widget.dart'; +import 'package:times_up_flutter/widgets/jh_line_chart.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_bottom_sheet.dart'; +import 'package:times_up_flutter/widgets/show_exeption_alert.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; class ChildDetailsPage extends StatefulWidget { const ChildDetailsPage({ @@ -39,10 +39,25 @@ class ChildDetailsPage extends StatefulWidget { static Future show(BuildContext context, ChildModel model) async { final database = Provider.of(context, listen: false); + await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => - ChildDetailsPage(database: database, childModel: model), + PageRouteBuilder( + pageBuilder: (context, animation, secondaryAnimation) { + return ChildDetailsPage(database: database, childModel: model); + }, + transitionsBuilder: (context, animation, secondaryAnimation, child) { + const begin = Offset(1, 0); + const end = Offset.zero; + const curve = Curves.easeInOut; + final tween = + Tween(begin: begin, end: end).chain(CurveTween(curve: curve)); + final offsetAnimation = animation.drive(tween); + return SlideTransition( + position: offsetAnimation, + child: child, + ); + }, + transitionDuration: const Duration(milliseconds: 400), ), ); } @@ -113,16 +128,52 @@ class _ChildDetailsPageState extends State return [ SliverAppBar( actions: [ - if (model.image != null) - Row( - children: [ - ClipOval( - child: Image.network(model.image!), + Row( + children: [ + GestureDetector( + onTap: () => showCustomBottomSheet( + context, + animationController: _animationController, + child: Container( + decoration: BoxDecoration( + color: themeData.scaffoldBackgroundColor, + ), + height: 200, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Spacer(), + JHCustomButton( + title: ' Bed Time', + backgroundColor: Colors.indigo, + onPress: () async => _sendNotification( + context, + model, + 'Hey Go to bed Now', + ), + ), + JHCustomButton( + title: 'Homework Time', + backgroundColor: CustomColors.indigoLight, + onPress: () async => _sendNotification( + context, + model, + 'Homework Time', + ), + ), + const Spacer(), + ], + ), + ), + ), + child: ClipOval( + child: model.image != null + ? Image.network(model.image!) + : const Icon(Icons.person), ).p4, - ], - ) - else - const SizedBox.shrink(), + ), + ], + ), ], elevation: 0.5, shadowColor: CustomColors.indigoLight, @@ -136,13 +187,6 @@ class _ChildDetailsPageState extends State iconTheme: const IconThemeData(color: Colors.red), backgroundColor: themeData.scaffoldBackgroundColor, expandedHeight: 50, - shape: ContinuousRectangleBorder( - side: BorderSide( - color: !value - ? themeData.scaffoldBackgroundColor - : CustomColors.indigoLight.withOpacity(0.5), - ), - ), pinned: true, floating: true, ), @@ -214,77 +258,19 @@ class _ChildDetailsPageState extends State margin: const EdgeInsets.symmetric(horizontal: 8), height: 205, width: double.infinity, - child: model.appsUsageModel.isNotEmpty - ? JHAppUsageChart( - isEmpty: false, - name: model.name, - ) - : JHAppUsageChart( - isEmpty: true, - name: model.name, - ), + child: JHLineChart(model: model), ), ], ).vTopP(20), - HeaderWidget( - title: AppLocalizations.of(context) - .sendNotificationToYourChildDevice, - subtitle: 'Push the button ', - ), - GestureDetector( - onTap: () => showCustomBottomSheet( - context, - animationController: _animationController, - child: Container( - decoration: BoxDecoration( - color: themeData.scaffoldBackgroundColor, - ), - height: 200, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Spacer(), - JHCustomButton( - title: ' Bed Time', - backgroundColor: Colors.indigo, - onPress: () async => _sendNotification( - context, - model, - 'Hey Go to bed Now', - ), - ), - JHCustomButton( - title: 'Homework Time', - backgroundColor: CustomColors.indigoLight, - onPress: () async => _sendNotification( - context, - model, - 'Homework Time', - ), - ), - const Spacer(), - ], - ), - ), - ), - child: const JHFeatureWidget( - title: 'Send Notification', - icon: Icons.wifi_tethering_error_sharp, - ), - ), - _AppUsedList( - model: model, - ), + _AppUsedList(model: model).vP16, JHCustomButton( title: 'Delete Child', - backgroundColor: Colors.transparent, - borderColor: Colors.red, - textColor: Colors.red, + backgroundColor: Colors.red, onPress: () async => _confirmDelete( context, widget.childModel, ), - ).vTopP(50), + ).vTopP(70), ]), ), ], @@ -327,9 +313,11 @@ class _ChildDetailsPageState extends State title: ' Hey ${model.name}', body: 'Here is a new message', message: content, + timeStamp: DateTime.now(), ), model, ); + Navigator.of(context).pop(); if (!mounted) return; await showAlertDialog( context, @@ -358,65 +346,82 @@ class _AppUsedList extends StatelessWidget { @override Widget build(BuildContext context) { final themeData = Theme.of(context); - return SingleChildScrollView( - child: ScrollConfiguration( - behavior: const ScrollBehavior().copyWith(overscroll: false), - child: Column( - children: [ - if (model.appsUsageModel.isNotEmpty) - const HeaderWidget( - title: 'Summary of used apps', - subtitle: 'Click for more details', - ) - else - const SizedBox.shrink().vP16, - if (model.appsUsageModel.isNotEmpty) - ListView.builder( - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: model.appsUsageModel.length, - itemBuilder: (context, index) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - ListTile( - leading: model.appsUsageModel[index].appIcon != null - ? Image.memory( - model.appsUsageModel[index].appIcon!, - height: 35, - ) - : const Icon(Icons.android), - title: Text( - model.appsUsageModel[index].appName, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - color: themeData.dividerColor, - ), - ), - trailing: Text( - model.appsUsageModel[index].usage.toString().t(), - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: themeData.dividerColor, - ), - ), - ), - ], - ); - }, - ) - else - const JHEmptyContent( - message: 'Seems like you have not set up the child device \n', - title: 'Set up the child device', - fontSizeMessage: 8, - fontSizeTitle: 12, + return Column( + children: [ + if (model.appsUsageModel.isNotEmpty) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const SizedBox( + width: 300, + child: HeaderWidget( + title: 'Summary of used apps', + subtitle: 'Click for more details', + ), ), - ], - ), - ), + TextButton( + child: Text( + 'See all', + style: TextStyle( + decoration: TextDecoration.underline, + color: CustomColors.indigoLight, + fontSize: 13, + ), + ), + onPressed: () => AppListPage.show(context, model), + ), + ], + ) + else + const SizedBox.shrink().vP16, + if (model.appsUsageModel.isNotEmpty) + ListView.builder( + padding: EdgeInsets.zero, + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: model.appsUsageModel.length > 5 + ? 5 + : (model.appsUsageModel.length * 0.20).toInt(), + itemBuilder: (context, index) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: model.appsUsageModel[index].appIcon != null + ? Image.memory( + model.appsUsageModel[index].appIcon!, + height: 35, + ) + : const Icon(Icons.android), + title: Text( + model.appsUsageModel[index].appName, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: themeData.dividerColor, + ), + ), + trailing: Text( + model.appsUsageModel[index].usage.toString().t(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: themeData.dividerColor, + ), + ), + ), + ], + ); + }, + ) + else + const JHEmptyContent( + message: 'Seems like you have not set up the child device \n', + title: 'Set up the child device', + fontSizeMessage: 8, + fontSizeTitle: 12, + ), + ], ); } } diff --git a/lib/app/pages/edit_child_page.dart b/lib/app/features/parent_side/edit_child_page.dart similarity index 79% rename from lib/app/pages/edit_child_page.dart rename to lib/app/features/parent_side/edit_child_page.dart index bf5bacc..302aefd 100644 --- a/lib/app/pages/edit_child_page.dart +++ b/lib/app/features/parent_side/edit_child_page.dart @@ -6,14 +6,14 @@ import 'package:firebase_storage/firebase_storage.dart'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart' as path; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/show_alert_dialog.dart'; -import 'package:times_up_flutter/common_widgets/show_exeption_alert.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_exeption_alert.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; import 'package:uuid/uuid.dart'; enum AppState { loading, complete } @@ -89,32 +89,33 @@ class _EditChildPageState extends State { Future _submit(XFile? localFile) async { if (appState == AppState.loading) return; if (_validateAndSaveForm()) { - if (localFile == null) return; setState(() { appState = AppState.loading; }); id = uuid.v4().substring(0, 8).toUpperCase(); - try { - final fileExtension = path.extension(localFile.path); - JHLogger.$.d(fileExtension); + if(localFile!=null) { + try { + final fileExtension = path.extension(localFile.path); + JHLogger.$.d(fileExtension); - final firebaseStorageRef = FirebaseStorage.instance - .ref() - .child('Child/"$id"/$id$fileExtension'); + final firebaseStorageRef = FirebaseStorage.instance + .ref() + .child('Child/"$id"/$id$fileExtension'); - await firebaseStorageRef - .putFile(File(localFile.path)) - .catchError((Function onError) { - JHLogger.$.e(onError); - // ignore: return_of_invalid_type_from_catch_error - return false; - }); - final url = await firebaseStorageRef.getDownloadURL(); - _imageURL = url; - JHLogger.$.d('download url: $url'); - } catch (e) { - JHLogger.$.d('...skipping image upload'); + await firebaseStorageRef + .putFile(File(localFile.path)) + .catchError((Function onError) { + JHLogger.$.e(onError); + // ignore: return_of_invalid_type_from_catch_error + return false; + }); + final url = await firebaseStorageRef.getDownloadURL(); + _imageURL = url; + JHLogger.$.d('download url: $url'); + } catch (e) { + JHLogger.$.d('...skipping image upload'); + } } try { @@ -128,7 +129,7 @@ class _EditChildPageState extends State { await showAlertDialog( context, title: ' Name already used', - content: 'Please choose a different job name', + content: 'Please choose a different child name', defaultActionText: 'OK', ); } @@ -162,6 +163,19 @@ class _EditChildPageState extends State { @override Widget build(BuildContext context) { return Scaffold( + resizeToAvoidBottomInset: false, + floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, + floatingActionButton: JHCustomRaisedButton( + width: MediaQuery.of(context).size.width * 0.80, + onPressed: () async => _submit(_imageFile), + color: CustomColors.indigoDark, + child: JHDisplayText( + text: 'Save', + style: TextStyle( + color: Theme.of(context).scaffoldBackgroundColor, + ), + ), + ).vP16, backgroundColor: Theme.of(context).scaffoldBackgroundColor.withOpacity(0.9), appBar: AppBar( @@ -183,15 +197,14 @@ class _EditChildPageState extends State { Widget _buildContents() { return !isSavedPressed - ? SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(16), - child: Card( - child: Padding( - padding: const EdgeInsets.all(16), - child: _buildForm(), - ), - ), + ? SizedBox( + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildForm().p(32), + const Spacer(), + ], ), ) : const Center(child: CircularProgressIndicator()); @@ -236,7 +249,7 @@ class _EditChildPageState extends State { ), onPressed: _getLocalImage, child: const JHDisplayText( - text: 'Upload', + text: 'Import ', style: TextStyle(color: Colors.white), ), ), @@ -258,15 +271,6 @@ class _EditChildPageState extends State { enabled: appState == AppState.complete || false, onSaved: (value) => _email = value, ), - JHCustomRaisedButton( - width: 200, - onPressed: () async => _submit(_imageFile), - color: Colors.indigo, - child: const JHDisplayText( - text: 'Save', - style: TextStyle(color: Colors.white), - ), - ).vTopP(24) ]; } diff --git a/lib/app/features/parent_side/language/language_notififier.dart b/lib/app/features/parent_side/language/language_notififier.dart new file mode 100644 index 0000000..b3d9af4 --- /dev/null +++ b/lib/app/features/parent_side/language/language_notififier.dart @@ -0,0 +1,77 @@ +import 'dart:ui'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:times_up_flutter/services/shared_preferences.dart'; + +class LanguageNotifier extends ChangeNotifier { + late String _selectedLanguage = '🇺🇸 English󠁢'; + late Locale _locale = const Locale('en'); + + String get selectedLanguage => _selectedLanguage; + Locale get locale => _locale; + + List languages = [ + '🇺🇸 English󠁢', + '🇫🇷 Français󠁢', + '🇪🇸 Español', + '🇹🇷 Turkish', + '🇩🇪 Deutsch', + ]; + + Future initLocalization() async { + _locale = await CacheService.getLocale(); + _setLanguageString(); + languages + ..insert(languages.indexOf(_selectedLanguage), _selectedLanguage) + ..removeWhere((element) => element == _selectedLanguage); + notifyListeners(); + } + + void selectLanguage(String language) { + languages.insert(languages.indexOf(language), _selectedLanguage); + _selectedLanguage = language; + languages.removeWhere((element) => element == _selectedLanguage); + _locale = setLocale(_selectedLanguage); + CacheService.setLocale(value: _locale); + HapticFeedback.heavyImpact(); + notifyListeners(); + } + + void _setLanguageString() { + switch (_locale.languageCode) { + case 'en': + _selectedLanguage = '🇺🇸 English󠁢'; + break; + case 'fr': + _selectedLanguage = '🇫🇷 Français󠁢'; + break; + case 'es': + _selectedLanguage = '🇪🇸 Español'; + break; + case 'de': + _selectedLanguage = '🇩🇪 Deutsch'; + break; + case 'tr': + _selectedLanguage = '🇹🇷 Turkish󠁢'; + break; + } + } + + Locale setLocale(String selectedLanguage) { + switch (selectedLanguage) { + case '🇫🇷 Français󠁢': + return const Locale('fr'); + case '🇺🇸 English󠁢': + return const Locale('en'); + case '🇪🇸 Español': + return const Locale('es'); + case '🇹🇷 Turkish': + return const Locale('tr'); + case '🇩🇪 Deutsch󠁢': + return const Locale('de'); + default: + return const Locale('en'); + } + } +} diff --git a/lib/app/features/parent_side/language/language_page.dart b/lib/app/features/parent_side/language/language_page.dart new file mode 100644 index 0000000..a56d237 --- /dev/null +++ b/lib/app/features/parent_side/language/language_page.dart @@ -0,0 +1,129 @@ +// ignore_for_file: library_private_types_in_public_api + +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:times_up_flutter/app/features/parent_side/language/language_notififier.dart'; +import 'package:times_up_flutter/services/auth.dart'; +import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; + +class LanguagePage extends StatefulWidget { + const LanguagePage({ + required this.auth, + required this.languageModel, + Key? key, + }) : super(key: key); + final AuthBase auth; + final LanguageNotifier languageModel; + + static Widget create( + BuildContext context, + AuthBase auth, + ) { + return Consumer( + builder: (BuildContext context, value, Widget? child) => LanguagePage( + auth: auth, + languageModel: value, + ), + ); + } + + @override + _LanguagePageState createState() => _LanguagePageState(); +} + +class _LanguagePageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final color = Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark; + + return Scaffold( + body: NestedScrollView( + headerSliverBuilder: (BuildContext context, value) { + return [ + SliverAppBar( + elevation: 0.5, + shadowColor: CustomColors.indigoLight, + iconTheme: IconThemeData(color: color), + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + expandedHeight: 50, + pinned: true, + floating: true, + ) + ]; + }, + body: ScrollConfiguration( + behavior: const ScrollBehavior().copyWith(overscroll: false), + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: JHDisplayText( + text: 'Languages', + style: TextStyle( + color: color, + fontWeight: FontWeight.w700, + ), + fontSize: 32, + maxFontSize: 34, + ).hP16, + ), + const SliverPadding(padding: EdgeInsets.only(top: 50)), + SliverToBoxAdapter( + child: Card( + margin: const EdgeInsets.symmetric(horizontal: 50), + color: CustomColors.indigoLight, + child: Center( + child: SizedBox( + height: 60, + child: Center( + child: JHDisplayText( + text: widget.languageModel.selectedLanguage, + style: TextStyles.title, + ), + ), + ), + ), + ), + ), + SliverList( + delegate: SliverChildListDelegate([ + SingleChildScrollView( + child: SizedBox( + height: 1000, + child: ListView.separated( + itemCount: 4, + itemBuilder: (builder, index) => GestureDetector( + onTap: () => widget.languageModel.selectLanguage( + widget.languageModel.languages[index], + ), + child: JHDisplayText( + text: widget.languageModel.languages[index], + style: TextStyles.title, + ).p(8), + ), + separatorBuilder: (BuildContext context, int index) => + const Divider( + thickness: 1, + indent: 20, + endIndent: 20, + color: Colors.grey, + ).p4, + ), + ), + ), + ]), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/app/config/geo_full.dart b/lib/app/features/parent_side/map_page.dart similarity index 93% rename from lib/app/config/geo_full.dart rename to lib/app/features/parent_side/map_page.dart index 15bc992..e7b3f21 100644 --- a/lib/app/config/geo_full.dart +++ b/lib/app/features/parent_side/map_page.dart @@ -8,19 +8,19 @@ import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/common_widgets/jh_animated_green_dot.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_header_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_pin_marker.dart'; +import 'package:times_up_flutter/app/helpers/marker_generator_helper.dart'; import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; -import 'package:times_up_flutter/services/marker_generator_service.dart'; import 'package:times_up_flutter/theme/theme.dart'; import 'package:times_up_flutter/utils/constants.dart'; +import 'package:times_up_flutter/widgets/jh_animated_green_dot.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_header_widget.dart'; +import 'package:times_up_flutter/widgets/jh_pin_marker.dart'; -class GeoFull extends StatefulWidget { - const GeoFull( +class MapView extends StatefulWidget { + const MapView( this.initialPosition, this.database, this.auth, @@ -46,7 +46,7 @@ class GeoFull extends StatefulWidget { listen: false, ); - return GeoFull( + return MapView( position, database, auth, @@ -56,10 +56,10 @@ class GeoFull extends StatefulWidget { } @override - State createState() => _GeoFullState(); + State createState() => _MapViewState(); } -class _GeoFullState extends State with SingleTickerProviderStateMixin { +class _MapViewState extends State with SingleTickerProviderStateMixin { final Completer _controller = Completer(); late final AnimationController _animationController; final _scaffoldKey = GlobalKey(); diff --git a/lib/app/pages/notification_page.dart b/lib/app/features/parent_side/notification_page.dart similarity index 65% rename from lib/app/pages/notification_page.dart rename to lib/app/features/parent_side/notification_page.dart index 1bba60e..48e2522 100644 --- a/lib/app/pages/notification_page.dart +++ b/lib/app/features/parent_side/notification_page.dart @@ -4,17 +4,19 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:line_awesome_flutter/line_awesome_flutter.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_loading_widget.dart'; -import 'package:times_up_flutter/common_widgets/show_exeption_alert.dart'; +import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; import 'package:times_up_flutter/models/notification_model/notification_model.dart'; import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/notification_service.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_loading_widget.dart'; +import 'package:times_up_flutter/widgets/show_exeption_alert.dart'; enum AppState { loaded, empty } +// class NotificationPage extends StatefulWidget { const NotificationPage({ required this.auth, @@ -49,7 +51,8 @@ class _NotificationPageState extends State { Future _delete(BuildContext context, NotificationModel model) async { try { - await widget.database?.deleteNotification(model.id!); + await widget.database + ?.deleteNotification(model.timeStamp!.toIso8601String()); } on FirebaseException catch (e) { await showExceptionAlertDialog( context, @@ -63,34 +66,22 @@ class _NotificationPageState extends State { void initState() { super.initState(); widget.auth.setToken(); - widget.notification?.configureFirebaseMessaging(); } @override Widget build(BuildContext context) { + final color = Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark; return NestedScrollView( headerSliverBuilder: (BuildContext context, value) { return [ SliverAppBar( elevation: 0.5, shadowColor: CustomColors.indigoLight, - title: const JHDisplayText( - text: 'Notifications', - style: TextStyle( - color: Colors.indigo, - fontWeight: FontWeight.w900, - ), - ), iconTheme: const IconThemeData(color: Colors.red), backgroundColor: Theme.of(context).scaffoldBackgroundColor, expandedHeight: 50, - shape: ContinuousRectangleBorder( - side: BorderSide( - color: !value - ? Theme.of(context).scaffoldBackgroundColor - : CustomColors.indigoLight.withOpacity(0.5), - ), - ), pinned: true, floating: true, ) @@ -100,6 +91,28 @@ class _NotificationPageState extends State { behavior: const ScrollBehavior().copyWith(overscroll: false), child: CustomScrollView( slivers: [ + SliverToBoxAdapter( + child: JHDisplayText( + text: 'Notifications', + style: TextStyle( + color: color, + fontWeight: FontWeight.w700, + ), + fontSize: 32, + maxFontSize: 34, + ).hP16, + ), + SliverToBoxAdapter( + child: JHDisplayText( + text: 'slide from right to left to dismiss', + style: TextStyle( + color: Colors.grey.withOpacity(0.5), + fontWeight: FontWeight.w400, + ), + fontSize: 12, + maxFontSize: 14, + ).hP16, + ), SliverList( delegate: SliverChildListDelegate([ SingleChildScrollView( @@ -114,9 +127,15 @@ class _NotificationPageState extends State { } Widget _buildStreamNotification(BuildContext context) { + final color = Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark; return StreamBuilder>( stream: widget.database?.notificationStream(), builder: (BuildContext context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SizedBox.shrink(); + } if (snapshot.hasData) { final data = snapshot.data; @@ -152,28 +171,34 @@ class _NotificationPageState extends State { }); }, direction: DismissDirection.endToStart, - child: Card( - color: CustomColors.indigoLight, - child: Padding( - padding: const EdgeInsets.all(8), - child: ListTile( - title: JHDisplayText( - text: data[index].title ?? 'No title available', - style: const TextStyle( - fontWeight: FontWeight.w600, - color: Colors.white, - fontSize: 16, - ), - ), - trailing: JHDisplayText( - text: - data[index].message ?? 'No message available', - style: const TextStyle( - fontWeight: FontWeight.w600, - color: Colors.white, - fontSize: 16, - ), - ), + child: ListTile( + contentPadding: + const EdgeInsets.symmetric(horizontal: 10), + subtitle: JHDisplayText( + textAlign: TextAlign.start, + text: convertToFormattedString( + data[index].timeStamp, + ), + style: const TextStyle( + fontWeight: FontWeight.w400, + color: Colors.grey, + fontSize: 14, + ), + ), + title: JHDisplayText( + text: data[index].title ?? 'No title available', + style: TextStyle( + fontWeight: FontWeight.w600, + color: color, + fontSize: 16, + ), + ), + trailing: JHDisplayText( + text: data[index].message ?? 'No message available', + style: TextStyle( + fontWeight: FontWeight.w600, + color: color, + fontSize: 16, ), ), ), diff --git a/lib/app/pages/parent_page.dart b/lib/app/features/parent_side/parent_page.dart similarity index 51% rename from lib/app/pages/parent_page.dart rename to lib/app/features/parent_side/parent_page.dart index b3910cc..587effa 100644 --- a/lib/app/pages/parent_page.dart +++ b/lib/app/features/parent_side/parent_page.dart @@ -6,21 +6,12 @@ import 'package:geolocator/geolocator.dart'; import 'package:line_awesome_flutter/line_awesome_flutter.dart'; import 'package:provider/provider.dart'; import 'package:showcaseview/showcaseview.dart'; -import 'package:times_up_flutter/app/config/geo_full.dart'; +import 'package:times_up_flutter/app/features/parent_side/child_details_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/edit_child_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/map_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/notification_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/setting_page.dart'; import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; -import 'package:times_up_flutter/app/pages/child_details_page.dart'; -import 'package:times_up_flutter/app/pages/edit_child_page.dart'; -import 'package:times_up_flutter/app/pages/notification_page.dart'; -import 'package:times_up_flutter/app/pages/setting_page.dart'; -import 'package:times_up_flutter/common_widgets/child_horizontal_view.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_empty_content.dart'; -import 'package:times_up_flutter/common_widgets/jh_header.dart'; -import 'package:times_up_flutter/common_widgets/jh_header_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_info_row_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_loading_widget.dart'; -import 'package:times_up_flutter/common_widgets/jh_summary_tile.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/l10n/l10n.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/services/api_path.dart'; @@ -32,6 +23,15 @@ import 'package:times_up_flutter/services/notification_service.dart'; import 'package:times_up_flutter/services/shared_preferences.dart'; import 'package:times_up_flutter/theme/theme.dart'; import 'package:times_up_flutter/utils/data.dart'; +import 'package:times_up_flutter/widgets/child_horizontal_view.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_empty_content.dart'; +import 'package:times_up_flutter/widgets/jh_header.dart'; +import 'package:times_up_flutter/widgets/jh_header_widget.dart'; +import 'package:times_up_flutter/widgets/jh_info_row_widget.dart'; +import 'package:times_up_flutter/widgets/jh_shimmer_map.dart'; +import 'package:times_up_flutter/widgets/jh_summary_tile.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; typedef ValueList = List>; @@ -85,6 +85,7 @@ class _ParentPageState extends State _getAverageUsage(); _getAllChildLocations(); _setShowCaseView(); + _scrollController = ScrollController(); _animationController = AnimationController( vsync: this, @@ -127,7 +128,7 @@ class _ParentPageState extends State Widget _buildNotificationPage(AuthBase auth) { return Provider( - create: (_) => NotificationService(), + create: (context) => NotificationService(), builder: (context, __) { return NotificationPage.create(context, auth); }, @@ -136,152 +137,127 @@ class _ParentPageState extends State Widget _buildDashboard(Database database, AuthBase auth) { final themeData = Theme.of(context); - return StreamBuilder>( - stream: database.childrenStream(), - builder: (context, AsyncSnapshot> snapshot) { - final data = snapshot.data; - if (snapshot.connectionState == ConnectionState.waiting) { - return const LoadingWidget(); - } else if (snapshot.hasError) { - return const JHEmptyContent( - title: 'Error Occurred !', - ); - } else { - return NestedScrollView( - controller: _scrollController, - headerSliverBuilder: (context, value) { - return [ - SliverAppBar( - elevation: 0.5, - shadowColor: CustomColors.indigoLight, - toolbarHeight: value ? 75 : 90, - flexibleSpace: - !value ? const JHHeader().hP16 : const SizedBox.shrink(), - backgroundColor: themeData.scaffoldBackgroundColor, - expandedHeight: !value ? 120 : 100, - shape: ContinuousRectangleBorder( - side: BorderSide( - color: !value - ? themeData.scaffoldBackgroundColor - : CustomColors.indigoDark.withOpacity(0.5), + return NestedScrollView( + controller: _scrollController, + headerSliverBuilder: (context, value) { + return [ + SliverAppBar( + elevation: 0.5, + shadowColor: CustomColors.indigoLight, + toolbarHeight: value ? 75 : 90, + flexibleSpace: + !value ? const JHHeader().hP16 : const SizedBox.shrink(), + backgroundColor: themeData.scaffoldBackgroundColor, + expandedHeight: !value ? 120 : 100, + title: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (!value) + const SizedBox.shrink() + else + JHDisplayText( + text: AppLocalizations.of(context).welcome, + style: const TextStyle( + color: Colors.indigo, + fontWeight: FontWeight.w900, ), ), - title: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (!value) - const SizedBox.shrink() - else - JHDisplayText( - text: AppLocalizations.of(context).welcome, - style: const TextStyle( - color: Colors.indigo, - fontWeight: FontWeight.w900, - ), - ), - GestureDetector( - onTap: () => SettingsPage.show(context, auth), - child: Showcase( - key: _settingsKey, - textColor: Colors.indigo, - description: AppLocalizations.of(context) - .changeTheSettingsHere, - child: const CircleAvatar( - child: Icon(Icons.person), - ), - ), - ), - ], - ), - pinned: true, - floating: true, - ), - ]; - }, - body: RefreshIndicator( - triggerMode: RefreshIndicatorTriggerMode.anywhere, - color: Theme.of(context).scaffoldBackgroundColor, - backgroundColor: Colors.indigo, - onRefresh: () => Future.wait([ - _getAverageUsage(), - _getAllChildLocations(), - _loadingTime(), - ]), - child: Scaffold( - floatingActionButton: Showcase( - key: _addKey, + Showcase( + key: _settingsKey, textColor: Colors.indigo, - description: AppLocalizations.of(context).addNewChildHere, - child: FloatingActionButton( - onPressed: () => EditChildPage.show( - context, - database: Provider.of(context, listen: false), - ), - backgroundColor: CustomColors.indigoLight, - child: const Icon(Icons.add), + description: + AppLocalizations.of(context).changeTheSettingsHere, + child: IconButton.outlined( + onPressed: () => SettingsPage.show(context, auth), + icon: const Icon(Icons.settings), + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoPrimary, ), ), - body: ScrollConfiguration( - behavior: const ScrollBehavior().copyWith(overscroll: false), - child: CustomScrollView( - slivers: [ - SliverList( - delegate: SliverChildListDelegate( - [ - HeaderWidget( - title: 'My Children', - subtitle: - 'Choose child to get more info - scroll ' - 'right', - trailing: IconButton( - icon: const Icon(Icons.info_outline), - onPressed: _startShowCase, - ), - ).hP4, - _buildChildrenList(database), - const HeaderWidget( - title: 'Get to see our child live app usage', - subtitle: 'Click on it to have the full report', - ).hP4, - JHSummaryTile( - title: formatDateTime(DateTime.now()), - time: data != null && data.isNotEmpty - ? _averageUsage.toString().t() - : '0h 0m', - progressValue: data != null && data.isNotEmpty - ? calculatePercentage(_averageUsage) - : 0, - ).vP4, - const HeaderWidget( - title: 'Information Section', - subtitle: 'Get tips on how to use the app.', - ).hP4, - JHInfoRow( - animationController: _animationController, - icon_1: Icons.auto_graph_outlined, - icon_2: Icons.message_outlined, - dataOne: MockData.text_1, - dataTwo: MockData.text_2, - ).p8, - JHInfoRow( - animationController: _animationController, - icon_1: Icons.lightbulb_rounded, - icon_2: Icons.volume_up_outlined, - dataOne: MockData.text_3, - dataTwo: MockData.text_4, - ).p8, - const SizedBox(height: 150), - ], + ], + ), + pinned: true, + floating: true, + ), + ]; + }, + body: RefreshIndicator( + triggerMode: RefreshIndicatorTriggerMode.anywhere, + color: Theme.of(context).scaffoldBackgroundColor, + backgroundColor: Colors.indigo, + onRefresh: () => Future.wait([ + _getAverageUsage(), + _getAllChildLocations(), + _loadingTime(), + ]), + child: Scaffold( + floatingActionButton: Showcase( + key: _addKey, + textColor: Colors.indigo, + description: AppLocalizations.of(context).addNewChildHere, + child: FloatingActionButton( + onPressed: () => EditChildPage.show( + context, + database: database, + ), + backgroundColor: CustomColors.greenPrimary, + child: const Icon(Icons.add), + ), + ), + body: ScrollConfiguration( + behavior: const ScrollBehavior().copyWith(overscroll: false), + child: CustomScrollView( + slivers: [ + SliverList( + delegate: SliverChildListDelegate( + [ + HeaderWidget( + title: 'My Children', + subtitle: 'Choose child to get more info - scroll ' + 'right', + trailing: IconButton( + icon: const Icon(Icons.info_outline), + onPressed: _startShowCase, ), - ), + ).hP4, + _buildChildrenList(database), + const HeaderWidget( + title: 'Get to see our child live app usage', + subtitle: 'Click on it to have the full report', + ).hP4, + JHSummaryTile( + title: formatDateTime(DateTime.now()), + time: _averageUsage.toString().t(), + progressValue: calculatePercentage(_averageUsage), + ).vP4, + const HeaderWidget( + title: 'Information Section', + subtitle: 'Get tips on how to use the app.', + ).hP4, + JHInfoRow( + animationController: _animationController, + icon_1: Icons.auto_graph_outlined, + icon_2: Icons.message_outlined, + dataOne: MockData.text_1, + dataTwo: MockData.text_2, + ).p8, + JHInfoRow( + animationController: _animationController, + icon_1: Icons.lightbulb_rounded, + icon_2: Icons.volume_up_outlined, + dataOne: MockData.text_3, + dataTwo: MockData.text_4, + ).p8, + const SizedBox(height: 150), ], ), ), - ), + ], ), - ); - } - }, + ), + ), + ), ); } @@ -303,7 +279,7 @@ class _ParentPageState extends State scrollDirection: Axis.horizontal, itemCount: data.length, itemBuilder: (context, index) { - return Kids( + return ChildListView( imageLocation: data[index]?.image, imageCaption: data[index]?.name, onPressed: () => @@ -317,7 +293,7 @@ class _ParentPageState extends State child: Icon(Icons.info_outline_rounded), ); } - } else if (snapshot.hasData) { + } else if (snapshot.hasError) { JHLogger.$.e(snapshot.error); return const Center( child: Row( @@ -340,7 +316,7 @@ class _ParentPageState extends State scrollDirection: Axis.horizontal, itemCount: 3, itemBuilder: (context, index) { - return const Kids(); + return const ChildListView(); }, ); }, @@ -353,23 +329,23 @@ class _ParentPageState extends State return Consumer( builder: (context, position, __) { return position != null - ? GeoFull.create( + ? MapView.create( context, position: position, database: database, auth: auth, locations: values, ) - : const LoadingWidget(); + : const ShimmerMap(); }, ); } Future _setShowCaseView() async { - final isVisited = await SharedPreference().getDisplayShowCase(); + final isVisited = await CacheService.getDisplayShowCase(); setState(() { _isShowCaseActivated = isVisited; - SharedPreference().setDisplayShowCase(); + CacheService.setDisplayShowCase(); }); if (!_isShowCaseActivated) { diff --git a/lib/app/features/parent_side/setting_page.dart b/lib/app/features/parent_side/setting_page.dart new file mode 100644 index 0000000..bce1b73 --- /dev/null +++ b/lib/app/features/parent_side/setting_page.dart @@ -0,0 +1,332 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/material.dart'; +import 'package:line_awesome_flutter/line_awesome_flutter.dart'; +import 'package:provider/provider.dart'; +import 'package:times_up_flutter/app/features/parent_side/language/language_page.dart'; +import 'package:times_up_flutter/services/app_info_service.dart'; +import 'package:times_up_flutter/services/auth.dart'; +import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/theme/theme_notifier.dart'; +import 'package:times_up_flutter/widgets/jh_custom_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_no_implementation.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; + +class SettingsPage extends StatelessWidget { + const SettingsPage({ + required this.auth, + required this.themeNotifier, + Key? key, + this.title, + this.name, + this.email, + this.context, + this.appInfoService, + }) : super(key: key); + final BuildContext? context; + final ThemeNotifier themeNotifier; + final AuthBase auth; + final String? title; + final String? name; + final String? email; + final AppInfoService? appInfoService; + + static Future show(BuildContext context, AuthBase auth) async { + final themeProvider = Provider.of(context, listen: false); + final appInfo = Provider.of(context, listen: false); + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SettingsPage( + context: context, + auth: auth, + themeNotifier: themeProvider, + appInfoService: appInfo, + ), + ), + ); + } + + Future _signOut(BuildContext context, AuthBase auth) async { + try { + await auth.signOut(); + } catch (e) { + JHLogger.$.e(e.toString()); + } + } + + Future confirmSignOut(BuildContext context, AuthBase auth) async { + final didRequestSignOut = await showAlertDialog( + context, + title: 'Logout', + content: 'Are you sure you want to log out?', + defaultActionText: 'Logout', + cancelActionText: 'Cancel', + ); + if (didRequestSignOut == true) { + await _signOut(context, auth); + Navigator.of(context).pop(); + } + } + + Widget buildItems(BuildContext context) { + return Expanded( + child: ListView( + physics: const BouncingScrollPhysics(), + children: [ + JHDisplayText( + text: 'Profile', + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, + fontWeight: FontWeight.w700, + ), + fontSize: 27, + maxFontSize: 34, + ).hP16, + ProfileListItem( + icon: LineAwesomeIcons.history, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Update profile', + ).vTopP(12), + ProfileListItem( + icon: LineAwesomeIcons.language, + onPressed: () => Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => LanguagePage.create(context, auth), + ), + ), + text: 'Change language', + ), + ProfileListItem( + icon: LineAwesomeIcons.bell, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Notification ', + ), + ProfileListItem( + onPressed: () {}, + icon: themeNotifier.isDarkMode + ? LineAwesomeIcons.moon + : LineAwesomeIcons.sun, + hasNavigation: false, + text: themeNotifier.isDarkMode ? 'Dark mode' : 'Light mode', + child: Switch( + activeColor: Colors.white, + inactiveThumbColor: Colors.white, + activeTrackColor: Colors.greenAccent, + inactiveTrackColor: Colors.red, + onChanged: (_) => themeNotifier.toggleTheme(), + value: themeNotifier.isDarkMode || false, + ), + ), + JHDisplayText( + text: 'Privacy', + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, + fontWeight: FontWeight.w700, + ), + fontSize: 27, + maxFontSize: 34, + ).hP16.vP16, + ProfileListItem( + icon: Icons.privacy_tip, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Privacy policy', + ), + ProfileListItem( + icon: Icons.file_copy, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Impressum', + ), + ProfileListItem( + icon: Icons.contact_page_outlined, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Terms and conditions', + isUnderLine: TextDecoration.underline, + ), + JHDisplayText( + text: 'Get us', + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, + fontWeight: FontWeight.w700, + ), + fontSize: 27, + maxFontSize: 34, + ).hP16.vP16, + ProfileListItem( + icon: Icons.rate_review, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Rate us', + ), + ProfileListItem( + icon: LineAwesomeIcons.bug, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Report Bug', + ), + ProfileListItem( + icon: Icons.contact_support_rounded, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Contact us', + ), + ProfileListItem( + icon: Icons.recommend, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'Recommend App', + ), + ProfileListItem( + icon: Icons.question_answer, + onPressed: () => showDialog( + context: context, + builder: (_) => const JHNoImplementationWidget(), + ), + text: 'FAQ', + ), + const Center( + child: JHDisplayText( + text: 'Copyright© JordyHers-org', + fontSize: 8, + maxFontSize: 12, + style: TextStyle(color: Colors.grey), + ), + ).vTopP(25), + Center( + child: JHDisplayText( + text: 'v${appInfoService?.appInfo.version ?? ''}', + fontSize: 8, + maxFontSize: 12, + style: const TextStyle(color: Colors.grey), + ), + ), + const SizedBox(height: 32) + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Theme.of(context).scaffoldBackgroundColor, + elevation: 0, + leading: IconButton( + onPressed: () => Navigator.pop(context), + icon: Icon( + Icons.close_outlined, + size: 23, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, + ), + ), + ), + body: Stack( + children: [ + Column( + children: [ + buildItems(context), + JHCustomButton( + title: 'Log out', + backgroundColor: Colors.red, + borderColor: Colors.red, + size: const Size(270, 50), + onPress: () async => confirmSignOut(context, auth), + ), + ], + ).vP8, + ], + ), + ); + } +} + +class ProfileListItem extends StatelessWidget { + const ProfileListItem({ + required this.onPressed, + Key? key, + this.icon, + this.text, + this.child, + this.isUnderLine = TextDecoration.none, + this.hasNavigation = true, + }) : super(key: key); + final IconData? icon; + final String? text; + final bool hasNavigation; + final TextDecoration? isUnderLine; + final VoidCallback onPressed; + final Widget? child; + + @override + Widget build(BuildContext context) { + return InkWell( + overlayColor: + MaterialStateColor.resolveWith((states) => Colors.transparent), + onTap: onPressed, + child: Container( + height: 45, + margin: const EdgeInsets.symmetric( + horizontal: 10, + ).copyWith( + bottom: 10, + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: Row( + children: [ + Icon( + icon, + size: 25, + ), + const SizedBox(width: 15), + JHDisplayText( + text: text ?? '', + style: TextStyles.body, + ), + const Spacer(), + if (child != null) child!, + if (hasNavigation) + const Icon( + Icons.chevron_right, + size: 25, + ), + ], + ), + ), + ); + } +} diff --git a/lib/sign_in/email_sign_in_bloc.dart b/lib/app/features/sign_in/email_sign_in_bloc.dart similarity index 89% rename from lib/sign_in/email_sign_in_bloc.dart rename to lib/app/features/sign_in/email_sign_in_bloc.dart index 265e752..6df238a 100644 --- a/lib/sign_in/email_sign_in_bloc.dart +++ b/lib/app/features/sign_in/email_sign_in_bloc.dart @@ -1,9 +1,8 @@ import 'dart:async'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_model.dart'; import 'package:times_up_flutter/services/auth.dart'; - -import 'package:times_up_flutter/sign_in/email_sign_in_model.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; class EmailSignInBloc { EmailSignInBloc({required this.auth}); @@ -43,6 +42,15 @@ class EmailSignInBloc { } } + + Future forgotPassword(String email) async{ + try { + return await auth.forgotPassword(email); + } catch (e) { + rethrow; + } + } + void toggleFormType() { final formType = _model.formType == EmailSignInFormType.signIn ? EmailSignInFormType.register diff --git a/lib/sign_in/email_sign_in_form_bloc_based.dart b/lib/app/features/sign_in/email_sign_in_form_bloc_based.dart similarity index 78% rename from lib/sign_in/email_sign_in_form_bloc_based.dart rename to lib/app/features/sign_in/email_sign_in_form_bloc_based.dart index f6cd298..99ff696 100644 --- a/lib/sign_in/email_sign_in_form_bloc_based.dart +++ b/lib/app/features/sign_in/email_sign_in_form_bloc_based.dart @@ -3,12 +3,14 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/common_widgets/jh_form_submit_button.dart'; -import 'package:times_up_flutter/common_widgets/show_exeption_alert.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_bloc.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_model.dart'; import 'package:times_up_flutter/services/auth.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_bloc.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_model.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_form_submit_button.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_exeption_alert.dart'; class EmailSignInFormBlocBased extends StatefulWidget { const EmailSignInFormBlocBased({ @@ -74,6 +76,37 @@ class _EmailSignInFormBlocBasedState extends State { } } + Future? _forgotPassword(EmailSignInModel model, String email) async { + try { + if (model.canResetPassword) { + final emailSent = await widget.bloc.forgotPassword(email); + if (emailSent) { + // ignore: use_build_context_synchronously + await showAlertDialog( + context, + title: 'Password reset email sent to $email', + content: 'Please check your email', + defaultActionText: 'OK', + ); + } else { + // ignore: use_build_context_synchronously + await showAlertDialog( + context, + title: 'Password reset failed', + content: 'There was some issue', + defaultActionText: 'OK', + ); + } + } + } on FirebaseAuthException catch (e) { + await showExceptionAlertDialog( + context, + title: model.signInFailedText, + exception: e, + ); + } + } + void _emailEditingComplete(EmailSignInModel model) { final newFocus = model.emailValidator.isValid(model.email) ? _passwordFocusNode @@ -105,6 +138,9 @@ class _EmailSignInFormBlocBasedState extends State { _buildEmailTextField(model), const SizedBox(height: 8), _buildPasswordTextField(model), + const SizedBox(height: 16), + if (model.formType == EmailSignInFormType.signIn) + _buildForgotPassword(model), const SizedBox(height: 8), FormSubmitButton( onPressed: () => model.canSubmitRegister || model.canSubmitSignIn @@ -203,6 +239,21 @@ class _EmailSignInFormBlocBasedState extends State { ); } + Widget _buildForgotPassword(EmailSignInModel model) { + return GestureDetector( + onTap: () => _forgotPassword(model, _emailController.text), + child: Container( + margin: const EdgeInsets.symmetric(vertical: 8), + width: double.infinity, + alignment: Alignment.centerRight, + child: JHDisplayText( + text: 'Forgot Password ?', + style: TextStyle(color: Colors.white.withOpacity(0.6), fontSize: 10), + ), + ), + ); + } + @override Widget build(BuildContext context) { return SingleChildScrollView( diff --git a/lib/sign_in/email_sign_in_model.dart b/lib/app/features/sign_in/email_sign_in_model.dart similarity index 93% rename from lib/sign_in/email_sign_in_model.dart rename to lib/app/features/sign_in/email_sign_in_model.dart index 258e09c..bbcc2bb 100644 --- a/lib/sign_in/email_sign_in_model.dart +++ b/lib/app/features/sign_in/email_sign_in_model.dart @@ -1,4 +1,4 @@ -import 'package:times_up_flutter/sign_in/validators.dart'; +import 'package:times_up_flutter/app/features/sign_in/validators.dart'; /// This enum takes care of the different states of the sign in form enum EmailSignInFormType { signIn, register } @@ -32,6 +32,10 @@ class EmailSignInModel with EmailAndPasswordValidators { !isLoading; } + bool get canResetPassword { + return emailValidator.isValid(email); + } + bool get canSubmitRegister { return emailValidator.isValid(email) && passwordValidator.isValid(password) && diff --git a/lib/sign_in/email_sign_in_page.dart b/lib/app/features/sign_in/email_sign_in_page.dart similarity index 89% rename from lib/sign_in/email_sign_in_page.dart rename to lib/app/features/sign_in/email_sign_in_page.dart index 006c141..692ab77 100644 --- a/lib/sign_in/email_sign_in_page.dart +++ b/lib/app/features/sign_in/email_sign_in_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_form_bloc_based.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_form_bloc_based.dart'; import 'package:times_up_flutter/theme/theme.dart'; class EmailSignInPage extends StatelessWidget { diff --git a/lib/app/features/sign_in/phone_sign_bloc_based.dart b/lib/app/features/sign_in/phone_sign_bloc_based.dart new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/app/features/sign_in/phone_sign_bloc_based.dart @@ -0,0 +1 @@ + diff --git a/lib/sign_in/sign_in_button.dart b/lib/app/features/sign_in/sign_in_button.dart similarity index 85% rename from lib/sign_in/sign_in_button.dart rename to lib/app/features/sign_in/sign_in_button.dart index d96b332..90b42e1 100644 --- a/lib/sign_in/sign_in_button.dart +++ b/lib/app/features/sign_in/sign_in_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class SignInButton extends JHCustomRaisedButton { SignInButton({ diff --git a/lib/sign_in/sign_in_manager.dart b/lib/app/features/sign_in/sign_in_manager.dart similarity index 100% rename from lib/sign_in/sign_in_manager.dart rename to lib/app/features/sign_in/sign_in_manager.dart diff --git a/lib/sign_in/sign_in_page.dart b/lib/app/features/sign_in/sign_in_page.dart similarity index 89% rename from lib/sign_in/sign_in_page.dart rename to lib/app/features/sign_in/sign_in_page.dart index 86b945b..e41fdfb 100644 --- a/lib/sign_in/sign_in_page.dart +++ b/lib/app/features/sign_in/sign_in_page.dart @@ -1,15 +1,15 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/show_exeption_alert.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_page.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_button.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_manager.dart'; +import 'package:times_up_flutter/app/features/sign_in/social_sign_in_button.dart'; import 'package:times_up_flutter/services/auth.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_page.dart'; -import 'package:times_up_flutter/sign_in/sign_in_button.dart'; -import 'package:times_up_flutter/sign_in/sign_in_manager.dart'; -import 'package:times_up_flutter/sign_in/social_sign_in_button.dart'; import 'package:times_up_flutter/utils/constants.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/show_exeption_alert.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; class SignInPage extends StatelessWidget { const SignInPage({ @@ -67,7 +67,6 @@ class SignInPage extends StatelessWidget { } Future _signInWithEmail(BuildContext context) async { - JHLogger.$.d('SIGNIN WITH EMAIL =>'); try { await Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/sign_in/social_sign_in_button.dart b/lib/app/features/sign_in/social_sign_in_button.dart similarity index 83% rename from lib/sign_in/social_sign_in_button.dart rename to lib/app/features/sign_in/social_sign_in_button.dart index 07285aa..64db670 100644 --- a/lib/sign_in/social_sign_in_button.dart +++ b/lib/app/features/sign_in/social_sign_in_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class SocialSignInButton extends JHCustomRaisedButton { SocialSignInButton({ diff --git a/lib/sign_in/validators.dart b/lib/app/features/sign_in/validators.dart similarity index 100% rename from lib/sign_in/validators.dart rename to lib/app/features/sign_in/validators.dart diff --git a/lib/app/splash/splash_content.dart b/lib/app/features/splash/splash_content.dart similarity index 98% rename from lib/app/splash/splash_content.dart rename to lib/app/features/splash/splash_content.dart index 5b26170..78baeb9 100644 --- a/lib/app/splash/splash_content.dart +++ b/lib/app/features/splash/splash_content.dart @@ -1,8 +1,8 @@ // ignore_for_file: library_private_types_in_public_api import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class SplashContent extends StatefulWidget { const SplashContent({ diff --git a/lib/app/splash/splash_screen.dart b/lib/app/features/splash/splash_screen.dart similarity index 87% rename from lib/app/splash/splash_screen.dart rename to lib/app/features/splash/splash_screen.dart index 04f3d1d..1c3ba30 100644 --- a/lib/app/splash/splash_screen.dart +++ b/lib/app/features/splash/splash_screen.dart @@ -2,13 +2,13 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -import 'package:times_up_flutter/app/landing_page.dart'; -import 'package:times_up_flutter/app/splash/splash_content.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_size_config.dart'; +import 'package:times_up_flutter/app/features/landing_page.dart'; +import 'package:times_up_flutter/app/features/splash/splash_content.dart'; import 'package:times_up_flutter/services/shared_preferences.dart'; import 'package:times_up_flutter/theme/theme.dart'; import 'package:times_up_flutter/utils/data.dart'; +import 'package:times_up_flutter/widgets/jh_custom_button.dart'; +import 'package:times_up_flutter/widgets/jh_size_config.dart'; class SplashScreen extends StatefulWidget { const SplashScreen({Key? key, this.context}) : super(key: key); @@ -59,8 +59,8 @@ class _SplashScreenState extends State { backgroundColor: Theme.of(context).primaryColor, title: 'Parent device'.toUpperCase(), onPress: () { - SharedPreference().setVisitingFlag(); - SharedPreference().setParentDevice(); + CacheService.setVisitingFlag(); + CacheService.setParentDevice(); Navigator.of(context).pushReplacement( CupertinoPageRoute( builder: (context) => const LandingPage(), @@ -73,9 +73,8 @@ class _SplashScreenState extends State { backgroundColor: CustomColors.greenPrimary, title: 'Child device'.toUpperCase(), onPress: () { - SharedPreference().setVisitingFlag(); - SharedPreference().setChildDevice(); - + CacheService.setVisitingFlag(); + CacheService.setChildDevice(); Navigator.of(context).pushReplacement( CupertinoPageRoute( builder: (context) => const LandingPage(), diff --git a/lib/services/marker_generator_service.dart b/lib/app/helpers/marker_generator_helper.dart similarity index 100% rename from lib/services/marker_generator_service.dart rename to lib/app/helpers/marker_generator_helper.dart diff --git a/lib/app/helpers/parsing_extension.dart b/lib/app/helpers/parsing_extension.dart index bf07a0f..c0d7183 100644 --- a/lib/app/helpers/parsing_extension.dart +++ b/lib/app/helpers/parsing_extension.dart @@ -1,5 +1,6 @@ +import 'dart:math'; + import 'package:intl/intl.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; extension ParseResult on String { String t() { @@ -134,6 +135,20 @@ double calculatePercentage(Duration duration) { final totalMilliseconds = totalDuration.inMilliseconds; final res = (milliseconds / totalMilliseconds) * 100; - JHLogger.$.d(parsedDuration); + //JHLogger.$.d(parsedDuration); return res; } + +int getRandom(int maxValue) { + const minRange = 1; + final maxRange = maxValue; + final random = Random(); + + return minRange + random.nextInt(maxRange - minRange + 1); +} + +String convertToFormattedString(DateTime? dateTime) { + final formattedDate = + dateTime != null ? DateFormat('MMMM d y hh:mm a').format(dateTime) : ''; + return formattedDate; +} diff --git a/lib/app/lifecycle/life_cycle.dart b/lib/app/lifecycle/life_cycle.dart index ff5ee61..abc1d27 100644 --- a/lib/app/lifecycle/life_cycle.dart +++ b/lib/app/lifecycle/life_cycle.dart @@ -1,25 +1,53 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; -class JHAppLifeCycleObserver extends WidgetsBindingObserver { +class JHAppLifecycleObserver extends StatefulWidget { + const JHAppLifecycleObserver({required this.child, Key? key}) + : super(key: key); + final Widget child; + + @override + // ignore: library_private_types_in_public_api + _JHAppLifecycleObserverState createState() => _JHAppLifecycleObserverState(); +} + +class _JHAppLifecycleObserverState extends State + with WidgetsBindingObserver { @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + @override + Future didChangeAppLifecycleState(AppLifecycleState state) async { switch (state) { case AppLifecycleState.resumed: - JHLogger.$.d(state.name); + JHLogger.$.d('${state.name} - result: '); break; case AppLifecycleState.inactive: - JHLogger.$.d(state.name); + JHLogger.$.e('${state.name} - result: '); + break; case AppLifecycleState.paused: - JHLogger.$.d(state.name); + JHLogger.$.e('${state.name} - result:'); + break; case AppLifecycleState.detached: - JHLogger.$.d(state.name); - // App is detached from the view hierarchy + JHLogger.$.e('${state.name} - result: '); + break; } } + + @override + Widget build(BuildContext context) { + return widget.child; + } } diff --git a/lib/app/pages/setting_page.dart b/lib/app/pages/setting_page.dart deleted file mode 100644 index 9af90ad..0000000 --- a/lib/app/pages/setting_page.dart +++ /dev/null @@ -1,229 +0,0 @@ -// ignore_for_file: use_build_context_synchronously - -import 'package:flutter/material.dart'; -import 'package:line_awesome_flutter/line_awesome_flutter.dart'; -import 'package:provider/provider.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_no_implementation.dart'; -import 'package:times_up_flutter/common_widgets/show_alert_dialog.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; -import 'package:times_up_flutter/services/app_info_service.dart'; -import 'package:times_up_flutter/services/auth.dart'; -import 'package:times_up_flutter/theme/theme.dart'; -import 'package:times_up_flutter/theme/theme_notifier.dart'; - -class SettingsPage extends StatelessWidget { - const SettingsPage({ - required this.auth, - required this.themeNotifier, - Key? key, - this.title, - this.name, - this.email, - this.context, - this.appInfoService, - }) : super(key: key); - final BuildContext? context; - final ThemeNotifier themeNotifier; - final AuthBase auth; - final String? title; - final String? name; - final String? email; - final AppInfoService? appInfoService; - - static Future show(BuildContext context, AuthBase auth) async { - final themeProvider = Provider.of(context, listen: false); - final appInfo = Provider.of(context, listen: false); - await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => SettingsPage( - context: context, - auth: auth, - themeNotifier: themeProvider, - appInfoService: appInfo, - ), - ), - ); - } - - Future _signOut(BuildContext context, AuthBase auth) async { - try { - await auth.signOut(); - } catch (e) { - JHLogger.$.e(e.toString()); - } - } - - Future confirmSignOut(BuildContext context, AuthBase auth) async { - final didRequestSignOut = await showAlertDialog( - context, - title: 'Logout', - content: 'Are you sure you want to log out?', - defaultActionText: 'Logout', - cancelActionText: 'Cancel', - ); - if (didRequestSignOut == true) { - await _signOut(context, auth); - Navigator.of(context).pop(); - } - } - - Widget buildItems(BuildContext context) { - return Expanded( - child: ListView( - physics: const BouncingScrollPhysics(), - children: [ - ProfileListItem( - icon: LineAwesomeIcons.history, - onPressed: () => showDialog( - context: context, - builder: (_) => const JHNoImplementationWidget(), - ), - text: 'Update profile', - ), - ProfileListItem( - icon: LineAwesomeIcons.language, - onPressed: () => showDialog( - context: context, - builder: (_) => const JHNoImplementationWidget(), - ), - text: 'Change language', - ), - ProfileListItem( - onPressed: () {}, - icon: themeNotifier.isDarkMode - ? LineAwesomeIcons.moon - : LineAwesomeIcons.sun, - hasNavigation: false, - text: themeNotifier.isDarkMode ? 'Dark mode' : 'Light mode', - child: Switch( - activeColor: Colors.white, - inactiveThumbColor: Colors.white, - activeTrackColor: Colors.greenAccent, - inactiveTrackColor: Colors.red, - onChanged: (_) => themeNotifier.toggleTheme(), - value: themeNotifier.isDarkMode || false, - ), - ), - ProfileListItem( - icon: LineAwesomeIcons.user_shield, - onPressed: () => showDialog( - context: context, - builder: (_) => const JHNoImplementationWidget(), - ), - text: 'Contact us', - ), - ], - ), - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Stack( - children: [ - Align( - alignment: Alignment.topCenter, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - onPressed: () => Navigator.pop(context), - icon: const Icon( - Icons.chevron_left, - size: 33, - ), - ), - IconButton( - onPressed: () => confirmSignOut(context, auth), - icon: const Icon( - Icons.logout, - size: 23, - ), - ), - ], - ), - buildItems(context), - const Center( - child: JHDisplayText( - text: 'Copyright© JordyHers-org', - fontSize: 8, - maxFontSize: 12, - style: TextStyle(color: Colors.grey), - ), - ), - Center( - child: JHDisplayText( - text: 'v${appInfoService?.appInfo.version ?? ''}', - fontSize: 8, - maxFontSize: 12, - style: const TextStyle(color: Colors.grey), - ), - ), - ], - ), - ).vP16.vP16, - ], - ), - ); - } -} - -class ProfileListItem extends StatelessWidget { - const ProfileListItem({ - required this.onPressed, - Key? key, - this.icon, - this.text, - this.child, - this.hasNavigation = true, - }) : super(key: key); - final IconData? icon; - final String? text; - final bool hasNavigation; - final VoidCallback onPressed; - final Widget? child; - - @override - Widget build(BuildContext context) { - return InkWell( - overlayColor: - MaterialStateColor.resolveWith((states) => Colors.transparent), - onTap: onPressed, - child: Container( - height: 55, - margin: const EdgeInsets.symmetric( - horizontal: 10, - ).copyWith( - bottom: 20, - ), - padding: const EdgeInsets.symmetric( - horizontal: 20, - ), - child: Row( - children: [ - Icon( - icon, - size: 25, - ), - const SizedBox(width: 15), - JHDisplayText( - text: text ?? '', - style: TextStyles.body, - ), - const Spacer(), - if (child != null) child!, - if (hasNavigation) - const Icon( - Icons.chevron_right, - size: 25, - ), - ], - ), - ), - ); - } -} diff --git a/lib/app/config/screencontroller_config.dart b/lib/app/screen_controller.dart similarity index 85% rename from lib/app/config/screencontroller_config.dart rename to lib/app/screen_controller.dart index 3f6f529..2166dad 100644 --- a/lib/app/config/screencontroller_config.dart +++ b/lib/app/screen_controller.dart @@ -2,13 +2,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/app/landing_page.dart'; -import 'package:times_up_flutter/common_widgets/jh_internet_connection_widget.dart'; +import 'package:times_up_flutter/app/features/landing_page.dart'; +import 'package:times_up_flutter/app/features/splash/splash_screen.dart'; import 'package:times_up_flutter/services/internet_connectivity_service.dart'; import 'package:times_up_flutter/services/shared_preferences.dart'; - -// ignore: always_use_package_imports -import '../splash/splash_screen.dart'; +import 'package:times_up_flutter/widgets/jh_internet_connection_widget.dart'; // ignore: must_be_immutable class ScreensController extends StatefulWidget { @@ -27,7 +25,7 @@ class _ScreensControllerState extends State { bool? _hasVisited; Future _setFlagValue() async { - final isVisited = await SharedPreference().getVisitingFlag(); + final isVisited = await CacheService.getVisitingFlag(); setState(() { _hasVisited = isVisited; }); diff --git a/lib/common_widgets/jh_custom_marker.dart b/lib/common_widgets/jh_custom_marker.dart deleted file mode 100644 index 749e028..0000000 --- a/lib/common_widgets/jh_custom_marker.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter/material.dart'; - -class JHCustomMarker extends StatelessWidget { - const JHCustomMarker({ - required this.markerUrl, - Key? key, - }) : super(key: key); - final String markerUrl; - - @override - Widget build(BuildContext context) { - return Image.network( - markerUrl, - height: 32, - width: 32, - color: Colors.indigo, - ); - } -} diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 69d1d0b..b3cb106 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,12 +1,15 @@ { - "@@locale": "de", - "welcome": "Wilkommen", - "changeTheSettingsHere": "Ändern Sie hier die Einstellungen", - "addNewChildHere": "Fügen Sie hier ein neues Kind hinzu", - "operationFailed": "Operation failed", - "enterThisCode": "Enter this code on the child's device", - "longPressToCopyOrDoubleTapToShare": "Long press to copy or double tap to share", - "enterThisCodeOnChildDevice": "Enter this code on child's device: ", - "sendNotificationToYourChildDevice": "Send notifications to your Child's device", - "copyText" : "Code Copied!" + "@@locale": "de", + "welcome": "Wilkommen", + "hello": "Hallo 👋", + "changeTheSettingsHere": "Ändern Sie hier die Einstellungen", + "addNewChildHere": "Fügen Sie hier ein neues Kind hinzu", + "operationFailed": "Operation failed", + "enterThisCode": "Enter this code on the child's device", + "longPressToCopyOrDoubleTapToShare": "Long press to copy or double tap to share", + "enterThisCodeOnChildDevice": "Enter this code on child's device: ", + "sendNotificationToYourChildDevice": "Send notifications to your Child's device", + "copyText" : "Code Copied!" + + } \ No newline at end of file diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 9aea62f..5dee837 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,6 +1,7 @@ { "@@locale": "en", "welcome": "Welcome", + "hello": "Hello 👋", "changeTheSettingsHere": "change the settings here", "addNewChildHere": "Add a new child here ", "operationFailed": "Operation failed", diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index 636b73d..8881d5f 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1,6 +1,7 @@ { "@@locale": "es", - "welcome": "Hola !", + "welcome": "Bienvenido !", + "hello": "Hola 👋", "changeTheSettingsHere": "Cambiar la configuración aquí", "addNewChildHere": "Añadir un nuevo niño[a] aquí", "operationFailed": "Operation failed", diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index 5f7664f..711c389 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -1,6 +1,7 @@ { "@@locale": "fr", "welcome": "Bienvenue", + "hello": "Salut 👋", "changeTheSettingsHere": "Modifier les paramètres ici", "addNewChildHere": "Ajoute un enfant ici", "operationFailed": "Operation failed", diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index d39ae05..3c26bff 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -1,6 +1,7 @@ { "@@locale": "tr", "welcome": "Hoş Geldiniz", + "hello": "Selam 👋", "changeTheSettingsHere": "Buradaki ayarları değiştir", "addNewChildHere": "Buraya yeni bir çocuk ekleyin", "operationFailed": "Operation failed", diff --git a/lib/main_development.dart b/lib/main_development.dart index 287672b..516286e 100644 --- a/lib/main_development.dart +++ b/lib/main_development.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:times_up_flutter/app/app.dart'; +import 'package:times_up_flutter/app/features/parent_side/language/language_notififier.dart'; import 'package:times_up_flutter/bootstrap.dart'; import 'package:times_up_flutter/firebase_options_dev.dart'; import 'package:times_up_flutter/services/app_info_service.dart'; @@ -17,10 +18,11 @@ import 'package:times_up_flutter/theme/theme_notifier.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - PackageInfo packageInfo = await PackageInfo.fromPlatform(); + late final packageInfo; await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ).whenComplete(() async { + packageInfo = await PackageInfo.fromPlatform(); await _notificationServiceListener(); }); @@ -42,7 +44,9 @@ Future main() async { ..getInitialConnectionStatus(), ), ChangeNotifierProvider( - create: (context) => ThemeNotifier()..toggleTheme()), + create: (context) => ThemeNotifier()..initThemeMode()), + ChangeNotifierProvider( + create: (context) => LanguageNotifier()..initLocalization(),), ChangeNotifierProvider( create: (context) => AppInfoService(packageInfo)), ], diff --git a/lib/main_production.dart b/lib/main_production.dart index 36c7e11..93d9cf2 100644 --- a/lib/main_production.dart +++ b/lib/main_production.dart @@ -17,10 +17,11 @@ import 'package:times_up_flutter/theme/theme_notifier.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - PackageInfo packageInfo = await PackageInfo.fromPlatform(); + late final packageInfo; await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ).whenComplete(() async { + packageInfo = await PackageInfo.fromPlatform(); await _notificationServiceListener(); }); @@ -42,7 +43,9 @@ Future main() async { ..getInitialConnectionStatus(), ), ChangeNotifierProvider( - create: (context) => ThemeNotifier()..toggleTheme()), + create: (context) => ThemeNotifier()..initThemeMode()), + ChangeNotifierProvider( + create: (context) => LanguageNotifier()), ChangeNotifierProvider( create: (context) => AppInfoService(packageInfo)), ], diff --git a/lib/main_staging.dart b/lib/main_staging.dart index 287672b..11fd5f8 100644 --- a/lib/main_staging.dart +++ b/lib/main_staging.dart @@ -17,10 +17,11 @@ import 'package:times_up_flutter/theme/theme_notifier.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - PackageInfo packageInfo = await PackageInfo.fromPlatform(); + late final packageInfo; await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ).whenComplete(() async { + packageInfo = await PackageInfo.fromPlatform(); await _notificationServiceListener(); }); @@ -42,7 +43,9 @@ Future main() async { ..getInitialConnectionStatus(), ), ChangeNotifierProvider( - create: (context) => ThemeNotifier()..toggleTheme()), + create: (context) => ThemeNotifier()..initThemeMode()), + ChangeNotifierProvider( + create: (context) => LanguageNotifier()), ChangeNotifierProvider( create: (context) => AppInfoService(packageInfo)), ], diff --git a/lib/models/notification_model/notification_model.dart b/lib/models/notification_model/notification_model.dart index 0a9a7bc..2ee288a 100644 --- a/lib/models/notification_model/notification_model.dart +++ b/lib/models/notification_model/notification_model.dart @@ -14,6 +14,7 @@ class NotificationModel with _$NotificationModel { required final String? body, required final String? message, required final String? id, + final DateTime? timeStamp, }) = _NotificationModel; factory NotificationModel.fromJson(Map json) => diff --git a/lib/models/notification_model/notification_model.freezed.dart b/lib/models/notification_model/notification_model.freezed.dart index b5f4d5d..5520d02 100644 --- a/lib/models/notification_model/notification_model.freezed.dart +++ b/lib/models/notification_model/notification_model.freezed.dart @@ -24,6 +24,7 @@ mixin _$NotificationModel { String? get body => throw _privateConstructorUsedError; String? get message => throw _privateConstructorUsedError; String? get id => throw _privateConstructorUsedError; + DateTime? get timeStamp => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -37,7 +38,12 @@ abstract class $NotificationModelCopyWith<$Res> { NotificationModel value, $Res Function(NotificationModel) then) = _$NotificationModelCopyWithImpl<$Res, NotificationModel>; @useResult - $Res call({String? title, String? body, String? message, String? id}); + $Res call( + {String? title, + String? body, + String? message, + String? id, + DateTime? timeStamp}); } /// @nodoc @@ -57,6 +63,7 @@ class _$NotificationModelCopyWithImpl<$Res, $Val extends NotificationModel> Object? body = freezed, Object? message = freezed, Object? id = freezed, + Object? timeStamp = freezed, }) { return _then(_value.copyWith( title: freezed == title @@ -75,6 +82,10 @@ class _$NotificationModelCopyWithImpl<$Res, $Val extends NotificationModel> ? _value.id : id // ignore: cast_nullable_to_non_nullable as String?, + timeStamp: freezed == timeStamp + ? _value.timeStamp + : timeStamp // ignore: cast_nullable_to_non_nullable + as DateTime?, ) as $Val); } } @@ -87,7 +98,12 @@ abstract class _$$_NotificationModelCopyWith<$Res> __$$_NotificationModelCopyWithImpl<$Res>; @override @useResult - $Res call({String? title, String? body, String? message, String? id}); + $Res call( + {String? title, + String? body, + String? message, + String? id, + DateTime? timeStamp}); } /// @nodoc @@ -105,6 +121,7 @@ class __$$_NotificationModelCopyWithImpl<$Res> Object? body = freezed, Object? message = freezed, Object? id = freezed, + Object? timeStamp = freezed, }) { return _then(_$_NotificationModel( title: freezed == title @@ -123,6 +140,10 @@ class __$$_NotificationModelCopyWithImpl<$Res> ? _value.id : id // ignore: cast_nullable_to_non_nullable as String?, + timeStamp: freezed == timeStamp + ? _value.timeStamp + : timeStamp // ignore: cast_nullable_to_non_nullable + as DateTime?, )); } } @@ -135,7 +156,8 @@ class _$_NotificationModel implements _NotificationModel { {required this.title, required this.body, required this.message, - required this.id}); + required this.id, + this.timeStamp}); factory _$_NotificationModel.fromJson(Map json) => _$$_NotificationModelFromJson(json); @@ -148,10 +170,12 @@ class _$_NotificationModel implements _NotificationModel { final String? message; @override final String? id; + @override + final DateTime? timeStamp; @override String toString() { - return 'NotificationModel(title: $title, body: $body, message: $message, id: $id)'; + return 'NotificationModel(title: $title, body: $body, message: $message, id: $id, timeStamp: $timeStamp)'; } @override @@ -162,12 +186,15 @@ class _$_NotificationModel implements _NotificationModel { (identical(other.title, title) || other.title == title) && (identical(other.body, body) || other.body == body) && (identical(other.message, message) || other.message == message) && - (identical(other.id, id) || other.id == id)); + (identical(other.id, id) || other.id == id) && + (identical(other.timeStamp, timeStamp) || + other.timeStamp == timeStamp)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, title, body, message, id); + int get hashCode => + Object.hash(runtimeType, title, body, message, id, timeStamp); @JsonKey(ignore: true) @override @@ -189,7 +216,8 @@ abstract class _NotificationModel implements NotificationModel { {required final String? title, required final String? body, required final String? message, - required final String? id}) = _$_NotificationModel; + required final String? id, + final DateTime? timeStamp}) = _$_NotificationModel; factory _NotificationModel.fromJson(Map json) = _$_NotificationModel.fromJson; @@ -203,6 +231,8 @@ abstract class _NotificationModel implements NotificationModel { @override String? get id; @override + DateTime? get timeStamp; + @override @JsonKey(ignore: true) _$$_NotificationModelCopyWith<_$_NotificationModel> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/models/notification_model/notification_model.g.dart b/lib/models/notification_model/notification_model.g.dart index a2b50bf..897207c 100644 --- a/lib/models/notification_model/notification_model.g.dart +++ b/lib/models/notification_model/notification_model.g.dart @@ -12,6 +12,9 @@ _$_NotificationModel _$$_NotificationModelFromJson(Map json) => body: json['body'] as String?, message: json['message'] as String?, id: json['id'] as String?, + timeStamp: json['timeStamp'] == null + ? null + : DateTime.parse(json['timeStamp'] as String), ); Map _$$_NotificationModelToJson( @@ -21,4 +24,5 @@ Map _$$_NotificationModelToJson( 'body': instance.body, 'message': instance.message, 'id': instance.id, + 'timeStamp': instance.timeStamp?.toIso8601String(), }; diff --git a/lib/models/set_child_model.dart b/lib/models/set_child_model.dart deleted file mode 100644 index d9f7de6..0000000 --- a/lib/models/set_child_model.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:times_up_flutter/sign_in/validators.dart'; - -class SetChildModel with EmailAndPasswordValidators { - SetChildModel({ - this.name = '', - this.email = '', - }); - - final String name; - final String email; - - SetChildModel copyWith({ - required String? name, - required String? email, - }) { - return SetChildModel( - email: email ?? this.email, - name: name ?? this.name, - ); - } -} diff --git a/lib/services/api_path.dart b/lib/services/api_path.dart index af61430..9b2f502 100644 --- a/lib/services/api_path.dart +++ b/lib/services/api_path.dart @@ -4,12 +4,13 @@ class APIPath { static String children(String uid) => 'users/$uid/child/'; - static String notifications(String uid, String childId) => - 'users/$uid/notifications/$childId'; + static String notifications(String uid, String timestamp) => + 'users/$uid/notifications/$timestamp'; static String notificationsStream(String uid, String childId) => 'users/$uid/notifications/'; static String mail() => 'mail/'; + static String deviceToken() => 'DeviceTokens/'; } diff --git a/lib/services/app_usage_service.dart b/lib/services/app_usage_service.dart index 11acd82..642f84b 100644 --- a/lib/services/app_usage_service.dart +++ b/lib/services/app_usage_service.dart @@ -1,10 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:installed_apps/app_info.dart'; import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/services/app_usage_local_service.dart'; import 'package:times_up_flutter/services/database.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; abstract class AppService { Future getAppUsageService(); @@ -51,7 +51,7 @@ class AppUsageService implements AppService { } } final averageDuration = getMedian(durations); - JHLogger.$.e(averageDuration); + // JHLogger.$.e(averageDuration); _averageDuration = averageDuration; return averageDuration; } diff --git a/lib/services/auth.dart b/lib/services/auth.dart index c27b474..fdc202e 100644 --- a/lib/services/auth.dart +++ b/lib/services/auth.dart @@ -4,7 +4,7 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_login_facebook/flutter_login_facebook.dart'; import 'package:google_sign_in/google_sign_in.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; abstract class AuthBase { User? get currentUser; @@ -33,6 +33,8 @@ abstract class AuthBase { String name, String surname, ); + + Future forgotPassword(String email); } class Auth implements AuthBase { @@ -186,4 +188,14 @@ class Auth implements AuthBase { _isFirstLogin = false; await _firebaseAuth.signOut(); } + + @override + Future forgotPassword(String email) async { + try { + await _firebaseAuth.sendPasswordResetEmail(email: email); + return true; + } catch (e) { + return false; + } + } } diff --git a/lib/services/database.dart b/lib/services/database.dart index 5e54f31..e196374 100644 --- a/lib/services/database.dart +++ b/lib/services/database.dart @@ -1,5 +1,4 @@ import 'package:cloud_firestore/cloud_firestore.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; import 'package:times_up_flutter/models/child_model/child_model.dart'; import 'package:times_up_flutter/models/email_model.dart'; import 'package:times_up_flutter/models/notification_model/notification_model.dart'; @@ -9,21 +8,20 @@ import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/firestore_service.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; import 'package:times_up_flutter/utils/constants.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; abstract class Database { - Future setChild(ChildModel model); + ChildModel? get currentChild; - Future liveUpdateChild( - ChildModel model, - int tick, - AppUsageService apps, - ); + Future setChild(ChildModel model); Future updateChild(ChildModel model); Future deleteChild(ChildModel model); - Future deleteNotification(String id); + Future deleteNotification(String timestamp); + + Future sendEmail({required EmailModel email}); Stream> childrenStream(); @@ -36,7 +34,10 @@ abstract class Database { ChildModel model, ); - Future sendEmail({required EmailModel email}); + Future liveUpdateChild( + ChildModel model, + AppUsageService apps, + ); Future getUserCurrentChild( String key, @@ -47,10 +48,11 @@ abstract class Database { } class FireStoreDatabase implements Database { - FireStoreDatabase({ - required this.uid, - required this.auth, - }) { + factory FireStoreDatabase({required String uid, required AuthBase auth}) { + return _singleton ??= FireStoreDatabase._internal(uid, auth); + } + + FireStoreDatabase._internal(this.uid, this.auth) { if (auth.isFirstLogin) { sendEmail( email: EmailModel( @@ -64,16 +66,15 @@ class FireStoreDatabase implements Database { ).then((value) => auth.setFirstLogin(isFirstLogin: false)); } } - + static FireStoreDatabase? _singleton; + GeoLocatorService geo = GeoLocatorService(); final String uid; final AuthBase auth; ChildModel? _child; - - ChildModel get currentChild => _child!; - final _service = FireStoreService.instance; - GeoLocatorService geo = GeoLocatorService(); + @override + ChildModel? get currentChild => _child; @override Future setChild(ChildModel model) => _service.setData( @@ -107,7 +108,7 @@ class FireStoreDatabase implements Database { } Future setTokenOnFireStore(Map token) async { - await _service.setNotificationFunction( + await _service.setTokenFunction( path: APIPath.deviceToken(), data: token, ); @@ -122,8 +123,8 @@ class FireStoreDatabase implements Database { } @override - Future deleteNotification(String id) async { - await _service.deleteData(path: APIPath.notifications(uid, id)); + Future deleteNotification(String timestamp) async { + await _service.deleteData(path: APIPath.notifications(uid, timestamp)); } @override @@ -150,26 +151,26 @@ class FireStoreDatabase implements Database { @override Future liveUpdateChild( ChildModel model, - int value, AppUsageService apps, ) async { await apps.getAppUsageService(); - // TODO(jordy): UNCOMMENT THIS TO UPDATE LOCATION - //var point = await geo.getInitialLocation(); - //var currentLocation = GeoPoint(point.latitude, point.longitude); + + final point = await geo.getCurrentLocation.last; + final currentLocation = GeoPoint(point.latitude, point.longitude); _child = ChildModel( id: model.id, name: model.name, email: model.email, token: model.token, - position: model.position, + position: currentLocation, appsUsageModel: apps.info, image: model.image, batteryLevel: model.batteryLevel, ); await updateChild(_child!); + JHLogger.$.e('Child Updated : $_child'); } @override diff --git a/lib/services/device_info_service.dart b/lib/services/device_info_service.dart deleted file mode 100644 index 29583dd..0000000 --- a/lib/services/device_info_service.dart +++ /dev/null @@ -1,3 +0,0 @@ -abstract class DeviceInfoServiceInterface {} - -class DeviceInfoService implements DeviceInfoServiceInterface {} diff --git a/lib/services/firestore_service.dart b/lib/services/firestore_service.dart index 203d9f6..3f8629a 100644 --- a/lib/services/firestore_service.dart +++ b/lib/services/firestore_service.dart @@ -2,8 +2,8 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_storage/firebase_storage.dart'; -import 'package:flutter/foundation.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/models/notification_model/notification_model.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; typedef QueryBuilder = T Function(Map data); @@ -26,7 +26,7 @@ class FireStoreService { required Map data, }) async { final reference = FirebaseFirestore.instance.doc(path); - JHLogger.$.d('$path: $data'); + JHLogger.$.e('$path: $data'); await reference.update(data); } @@ -34,6 +34,18 @@ class FireStoreService { Future setNotificationFunction({ required String path, required Map data, + }) async { + final reference = FirebaseFirestore.instance + .collection(path) + .doc(data['timeStamp'] as String); + JHLogger.$.d('$path: $data'); + + await reference.set(data); + } + + Future setTokenFunction({ + required String path, + required Map data, }) async { final reference = FirebaseFirestore.instance.collection(path).doc(data['id'] as String?); @@ -68,8 +80,11 @@ class FireStoreService { await reference.delete(); } - debugPrint('delete: $path'); - await reference.delete(); + try { + await reference.delete(); + } catch (e) { + JHLogger.$.e(e); + } } Stream> collectionStream({ @@ -100,6 +115,7 @@ class FireStoreService { Stream> notificationStream({ required String path, required T Function(Map data, String documentId) builder, + String? childId, Function(Query query)? queryBuilder, int Function(T lhs, T rhs)? sort, }) { @@ -108,10 +124,15 @@ class FireStoreService { query = queryBuilder(query) as CollectionReference>; } final snapshots = query.snapshots(); + return snapshots.map((snapshot) { final result = snapshot.docs .map((snapshot) => builder(snapshot.data(), snapshot.id)) - .where((value) => value != null) + .where( + (value) => + value != null && + (childId == null || (value as NotificationModel).id == childId), + ) .toList(); if (sort != null) { result.sort(sort); diff --git a/lib/services/geo_locator_service.dart b/lib/services/geo_locator_service.dart index 862b2f5..c5e87d5 100644 --- a/lib/services/geo_locator_service.dart +++ b/lib/services/geo_locator_service.dart @@ -15,6 +15,7 @@ class GeoLocatorService { Future getInitialLocation() async { permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { return Future.error('Location permissions are denied'); } diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index f1351a3..cde4f92 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -1,18 +1,59 @@ // ignore_for_file: avoid_dynamic_calls +import 'dart:async'; +import 'dart:ui'; + import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_background_service/flutter_background_service.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:times_up_flutter/common_widgets/show_logger.dart'; +import 'package:times_up_flutter/widgets/show_logger.dart'; + +const notificationChannelId = 'high_importance_channel'; +const notificationChannelTitle = 'High Importance Notifications'; +const notificationId = 800; const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'high_importance_channel', // id - 'High Importance Notifications', // title + notificationChannelId, + notificationChannelTitle, description: 'This channel is used for important notifications.', - // description importance: Importance.max, ); +@pragma('vm:entry-point') +Future onStart(ServiceInstance service) async { + DartPluginRegistrant.ensureInitialized(); + service.on('stopService').listen((event) { + service.stopSelf(); + }); + Timer.periodic(const Duration(minutes: 15), (timer) async { + // TODO(JORDY): IMPLEMENT DATA UPDATE HERE + // final databaseStore = Provider.of(context!, listen: false); + // final appUsage = Provider.of(context, listen: false); + // JHLogger.$.e(' NOT READy '); + // if (databaseStore.currentChild != null) { + // await databaseStore.liveUpdateChild( + // databaseStore.currentChild!, + // appUsage, + // ); + // } + await NotificationService.flutterLocalNotificationsPlugin.show( + notificationId, + 'Times Up - Monitoring', + 'Tracking App Usage and live location', + const NotificationDetails( + android: AndroidNotificationDetails( + notificationChannelId, + notificationChannelTitle, + icon: 'parental_launch', + ongoing: true, + importance: Importance.max, + ), + ), + ); + }); +} + class NotificationService { factory NotificationService() { return _singleton; @@ -22,21 +63,38 @@ class NotificationService { static final NotificationService _singleton = NotificationService._internal(); // Here the set up for cloud Messaging Android is being configured - final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); - // The LocalPlugin method configures the android channel - Future localPlugin() async => await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - Future initialize() async { - await flutterLocalNotificationsPlugin.initialize( - const InitializationSettings( - android: AndroidInitializationSettings('@drawable/parental_launch'), - ), - ); + try { + final service = FlutterBackgroundService(); + await flutterLocalNotificationsPlugin + .initialize( + const InitializationSettings( + android: AndroidInitializationSettings('@drawable/parental_launch'), + ), + ) + .then((value) async { + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + + await service.configure( + iosConfiguration: IosConfiguration(), + androidConfiguration: AndroidConfiguration( + onStart: onStart, + isForegroundMode: true, + initialNotificationTitle: 'Times Up Flutter Launched', + initialNotificationContent: 'The app is tracking metadata', + foregroundServiceNotificationId: notificationId, + ), + ); + }); + } catch (e) { + JHLogger.$.e(e); + } } // Create a new instance of Firebase Messaging @@ -46,7 +104,6 @@ class NotificationService { final androidImplementation = flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>(); - await androidImplementation?.requestPermission(); } diff --git a/lib/services/shared_preferences.dart b/lib/services/shared_preferences.dart index f74b755..33a5f41 100644 --- a/lib/services/shared_preferences.dart +++ b/lib/services/shared_preferences.dart @@ -1,42 +1,68 @@ +import 'dart:ui'; + import 'package:shared_preferences/shared_preferences.dart'; -class SharedPreference { - Future getVisitingFlag() async { +class CacheService { + static Future getVisitingFlag() async { final preferences = await SharedPreferences.getInstance(); final alreadyVisited = preferences.getBool('alreadyVisited') ?? false; return alreadyVisited; } - Future getParentOrChild() async { + static Future getParentOrChild() async { final preferences = await SharedPreferences.getInstance(); final isParent = preferences.getBool('isParent') ?? true; return isParent; } - Future getDisplayShowCase() async { + static Future getDisplayShowCase() async { final preferences = await SharedPreferences.getInstance(); final displayShowCase = preferences.getBool('displayShowCase') ?? false; return displayShowCase; } - Future setVisitingFlag() async { + static Future getThemeMode() async { + final preferences = await SharedPreferences.getInstance(); + final darkMode = preferences.getBool('isDarkMode') ?? false; + return darkMode; + } + + static Future getLocale() async { + final preferences = await SharedPreferences.getInstance(); + final status = preferences.getString('locale') ?? 'en'; + return Locale(status); + } + + static Future setVisitingFlag() async { final preferences = await SharedPreferences.getInstance(); await preferences.setBool('alreadyVisited', true); } - Future setParentDevice() async { + static Future setParentDevice() async { final preferences = await SharedPreferences.getInstance(); await preferences.setBool('isParent', true); } - Future setChildDevice() async { + static Future setChildDevice() async { final preferences = await SharedPreferences.getInstance(); await preferences.setBool('isParent', false); } - Future setDisplayShowCase() async { + static Future setDisplayShowCase() async { final preferences = await SharedPreferences.getInstance(); final status = await preferences.setBool('displayShowCase', true); return status; } + + static Future setTheDarkTheme({required bool value}) async { + final preferences = await SharedPreferences.getInstance(); + final status = await preferences.setBool('isDarkMode', value); + return status; + } + + static Future setLocale({required Locale value}) async { + final preferences = await SharedPreferences.getInstance(); + final locale = await preferences.setString('locale', value.languageCode); + return locale; + } } diff --git a/lib/theme/theme.dart b/lib/theme/theme.dart index b581bc1..02e36b3 100644 --- a/lib/theme/theme.dart +++ b/lib/theme/theme.dart @@ -41,6 +41,7 @@ class AppTheme { ); static ThemeData darkTheme = ThemeData( + //textTheme: GoogleFonts.interTextTheme(), primarySwatch: buildMaterialColor(CustomColors.indigoDark), primaryColor: CustomColors.indigoDark, scaffoldBackgroundColor: CustomColors.indigoDarker, @@ -49,6 +50,10 @@ class AppTheme { cardTheme: CardTheme(color: CustomColors.indigoDarker), iconTheme: IconThemeData(color: CustomColors.indigoLight), dividerColor: CustomColors.indigoLight, + bottomNavigationBarTheme: BottomNavigationBarThemeData( + selectedItemColor: CustomColors.greenPrimary, + unselectedItemColor: CustomColors.indigoLight, + ), floatingActionButtonTheme: FloatingActionButtonThemeData( backgroundColor: CustomColors.indigoLight, foregroundColor: CustomColors.indigoDark, diff --git a/lib/theme/theme_notifier.dart b/lib/theme/theme_notifier.dart index 008b6b5..5d7c155 100644 --- a/lib/theme/theme_notifier.dart +++ b/lib/theme/theme_notifier.dart @@ -1,12 +1,19 @@ import 'package:flutter/material.dart'; +import 'package:times_up_flutter/services/shared_preferences.dart'; class ThemeNotifier extends ChangeNotifier { bool _isDarkMode = false; bool get isDarkMode => _isDarkMode; + Future initThemeMode() async { + _isDarkMode = await CacheService.getThemeMode(); + notifyListeners(); + } + void toggleTheme() { _isDarkMode = !_isDarkMode; + CacheService.setTheDarkTheme(value: _isDarkMode); notifyListeners(); } } diff --git a/lib/utils/constants.dart b/lib/utils/constants.dart index eac0963..ad275c1 100644 --- a/lib/utils/constants.dart +++ b/lib/utils/constants.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; - import 'package:times_up_flutter/utils/app_strings.dart'; class Keys { diff --git a/lib/common_widgets/child_horizontal_view.dart b/lib/widgets/child_horizontal_view.dart similarity index 97% rename from lib/common_widgets/child_horizontal_view.dart rename to lib/widgets/child_horizontal_view.dart index 8a4bdcb..5f1e157 100644 --- a/lib/common_widgets/child_horizontal_view.dart +++ b/lib/widgets/child_horizontal_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -class Kids extends StatelessWidget { - const Kids({ +class ChildListView extends StatelessWidget { + const ChildListView({ this.imageLocation, this.imageCaption, Key? key, diff --git a/lib/common_widgets/jh_animated_green_dot.dart b/lib/widgets/jh_animated_green_dot.dart similarity index 100% rename from lib/common_widgets/jh_animated_green_dot.dart rename to lib/widgets/jh_animated_green_dot.dart diff --git a/lib/common_widgets/jh_bar_chart.dart b/lib/widgets/jh_bar_chart.dart similarity index 73% rename from lib/common_widgets/jh_bar_chart.dart rename to lib/widgets/jh_bar_chart.dart index 4977e05..5e9cebc 100644 --- a/lib/common_widgets/jh_bar_chart.dart +++ b/lib/widgets/jh_bar_chart.dart @@ -1,9 +1,7 @@ -import 'dart:math'; - import 'package:fl_chart/fl_chart.dart'; import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHAppUsageChart extends StatefulWidget { const JHAppUsageChart({ @@ -11,14 +9,7 @@ class JHAppUsageChart extends StatefulWidget { required this.name, Key? key, }) : super(key: key); - List get availableColors => const [ - Colors.purpleAccent, - Colors.yellow, - Colors.lightBlue, - Colors.orange, - Colors.pink, - Colors.redAccent, - ]; + final bool isEmpty; final String name; @@ -31,8 +22,6 @@ class JHAppUsageChartState extends State { int touchedIndex = -1; - bool isPlaying = false; - @override Widget build(BuildContext context) { return AspectRatio( @@ -73,7 +62,7 @@ class JHAppUsageChartState extends State { child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: BarChart( - isPlaying ? randomData() : mainBarData(), + mainBarData(), swapAnimationDuration: animDuration, ), ), @@ -84,26 +73,6 @@ class JHAppUsageChartState extends State { ], ), ), - Padding( - padding: const EdgeInsets.all(4), - child: Align( - alignment: Alignment.topRight, - child: IconButton( - icon: Icon( - isPlaying ? Icons.pause : Icons.play_arrow, - color: Colors.pinkAccent, - ), - onPressed: () { - setState(() { - isPlaying = !isPlaying; - if (isPlaying) { - refreshState(); - } - }); - }, - ), - ), - ), ], ), ), @@ -349,72 +318,7 @@ class JHAppUsageChartState extends State { borderData: FlBorderData( show: false, ), - barGroups: List.generate(7, (i) { - switch (i) { - case 0: - return makeGroupData( - 0, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 1: - return makeGroupData( - 1, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 2: - return makeGroupData( - 2, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 3: - return makeGroupData( - 3, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 4: - return makeGroupData( - 4, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 5: - return makeGroupData( - 5, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - case 6: - return makeGroupData( - 6, - Random().nextInt(15).toDouble() + 6, - barColor: widget.availableColors[ - Random().nextInt(widget.availableColors.length)], - ); - default: - return throw Error(); - } - }), gridData: FlGridData(show: false), ); } - - Future refreshState() async { - setState(() {}); - await Future.delayed( - animDuration + const Duration(milliseconds: 50), - ); - if (isPlaying) { - await refreshState(); - } - } } diff --git a/lib/common_widgets/jh_battery_widget.dart b/lib/widgets/jh_battery_widget.dart similarity index 100% rename from lib/common_widgets/jh_battery_widget.dart rename to lib/widgets/jh_battery_widget.dart diff --git a/lib/common_widgets/jh_custom_button.dart b/lib/widgets/jh_custom_button.dart similarity index 75% rename from lib/common_widgets/jh_custom_button.dart rename to lib/widgets/jh_custom_button.dart index 47dbd2c..7b026f2 100644 --- a/lib/common_widgets/jh_custom_button.dart +++ b/lib/widgets/jh_custom_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_size_config.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_size_config.dart'; class JHCustomButton extends StatelessWidget { const JHCustomButton({ @@ -11,12 +11,14 @@ class JHCustomButton extends StatelessWidget { Key? key, this.borderColor = Colors.transparent, this.textColor = Colors.white, + this.size, }) : super(key: key); final Color backgroundColor; final Color borderColor; final Color textColor; - final void Function() onPress; + final Size? size; final String title; + final void Function() onPress; @override Widget build(BuildContext context) { @@ -26,10 +28,11 @@ class JHCustomButton extends StatelessWidget { style: OutlinedButton.styleFrom( backgroundColor: backgroundColor, side: BorderSide(width: 1.5, color: borderColor), - minimumSize: Size( - MediaQuery.of(context).size.width * 0.95, - JHSizeConfig.screenHeight! * 0.07, - ), + minimumSize: size ?? + Size( + MediaQuery.of(context).size.width * 0.95, + JHSizeConfig.screenHeight! * 0.07, + ), ), onPressed: onPress, child: JHDisplayText( diff --git a/lib/common_widgets/jh_custom_raised_button.dart b/lib/widgets/jh_custom_raised_button.dart similarity index 100% rename from lib/common_widgets/jh_custom_raised_button.dart rename to lib/widgets/jh_custom_raised_button.dart diff --git a/lib/common_widgets/jh_display_text.dart b/lib/widgets/jh_display_text.dart similarity index 87% rename from lib/common_widgets/jh_display_text.dart rename to lib/widgets/jh_display_text.dart index ee80a1b..b489e67 100644 --- a/lib/common_widgets/jh_display_text.dart +++ b/lib/widgets/jh_display_text.dart @@ -9,9 +9,11 @@ class JHDisplayText extends StatelessWidget { this.fontSize, this.maxFontSize, this.maxLines, + this.textAlign, }) : super(key: key); final String text; final TextStyle style; + final TextAlign? textAlign; final double? fontSize; final double? maxFontSize; final int? maxLines; @@ -23,6 +25,7 @@ class JHDisplayText extends StatelessWidget { style: style, maxLines: maxLines ?? 10, textScaleFactor: 1, + textAlign: textAlign ?? TextAlign.start, minFontSize: fontSize ?? 13, maxFontSize: maxFontSize ?? 35, ); diff --git a/lib/common_widgets/jh_empty_content.dart b/lib/widgets/jh_empty_content.dart similarity index 93% rename from lib/common_widgets/jh_empty_content.dart rename to lib/widgets/jh_empty_content.dart index 78d1894..019478a 100644 --- a/lib/common_widgets/jh_empty_content.dart +++ b/lib/widgets/jh_empty_content.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHEmptyContent extends StatelessWidget { const JHEmptyContent({ diff --git a/lib/common_widgets/jh_feature_widget.dart b/lib/widgets/jh_feature_widget.dart similarity index 96% rename from lib/common_widgets/jh_feature_widget.dart rename to lib/widgets/jh_feature_widget.dart index 5b42a4a..bd9e9ce 100644 --- a/lib/common_widgets/jh_feature_widget.dart +++ b/lib/widgets/jh_feature_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHFeatureWidget extends StatelessWidget { const JHFeatureWidget({ diff --git a/lib/common_widgets/jh_form_submit_button.dart b/lib/widgets/jh_form_submit_button.dart similarity index 79% rename from lib/common_widgets/jh_form_submit_button.dart rename to lib/widgets/jh_form_submit_button.dart index 30e3d3f..ef859ae 100644 --- a/lib/common_widgets/jh_form_submit_button.dart +++ b/lib/widgets/jh_form_submit_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class FormSubmitButton extends JHCustomRaisedButton { FormSubmitButton({ diff --git a/lib/common_widgets/jh_header.dart b/lib/widgets/jh_header.dart similarity index 81% rename from lib/common_widgets/jh_header.dart rename to lib/widgets/jh_header.dart index d8d7718..ba517df 100644 --- a/lib/common_widgets/jh_header.dart +++ b/lib/widgets/jh_header.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; +import 'package:times_up_flutter/l10n/l10n.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHHeader extends StatelessWidget { const JHHeader({ @@ -18,7 +19,7 @@ class JHHeader extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ JHDisplayText( - text: 'Hello 👋', + text: AppLocalizations.of(context).hello, fontSize: fontSize, maxFontSize: maxFontSize, style: TextStyle( @@ -27,7 +28,7 @@ class JHHeader extends StatelessWidget { ), ), JHDisplayText( - text: 'Welcome', + text: AppLocalizations.of(context).welcome, fontSize: fontSize, maxFontSize: maxFontSize, style: TextStyle( diff --git a/lib/common_widgets/jh_header_widget.dart b/lib/widgets/jh_header_widget.dart similarity index 91% rename from lib/common_widgets/jh_header_widget.dart rename to lib/widgets/jh_header_widget.dart index 2b9f39d..edf013f 100644 --- a/lib/common_widgets/jh_header_widget.dart +++ b/lib/widgets/jh_header_widget.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class HeaderWidget extends StatelessWidget { const HeaderWidget({ diff --git a/lib/common_widgets/jh_info_bottom_sheet.dart b/lib/widgets/jh_info_bottom_sheet.dart similarity index 92% rename from lib/common_widgets/jh_info_bottom_sheet.dart rename to lib/widgets/jh_info_bottom_sheet.dart index 2e6f278..f1068a9 100644 --- a/lib/common_widgets/jh_info_bottom_sheet.dart +++ b/lib/widgets/jh_info_bottom_sheet.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHBottomSheet extends StatelessWidget { const JHBottomSheet({ diff --git a/lib/common_widgets/jh_info_box.dart b/lib/widgets/jh_info_box.dart similarity index 95% rename from lib/common_widgets/jh_info_box.dart rename to lib/widgets/jh_info_box.dart index 2cf7175..00eb45d 100644 --- a/lib/common_widgets/jh_info_box.dart +++ b/lib/widgets/jh_info_box.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_size_config.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_size_config.dart'; typedef TriggerFunction = void Function()?; diff --git a/lib/common_widgets/jh_info_row_widget.dart b/lib/widgets/jh_info_row_widget.dart similarity index 86% rename from lib/common_widgets/jh_info_row_widget.dart rename to lib/widgets/jh_info_row_widget.dart index dee7844..94172a7 100644 --- a/lib/common_widgets/jh_info_row_widget.dart +++ b/lib/widgets/jh_info_row_widget.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_info_bottom_sheet.dart'; -import 'package:times_up_flutter/common_widgets/jh_info_box.dart'; -import 'package:times_up_flutter/common_widgets/show_bottom_sheet.dart'; import 'package:times_up_flutter/theme/theme.dart'; import 'package:times_up_flutter/utils/data.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_info_bottom_sheet.dart'; +import 'package:times_up_flutter/widgets/jh_info_box.dart'; +import 'package:times_up_flutter/widgets/show_bottom_sheet.dart'; class JHInfoRow extends StatelessWidget { const JHInfoRow({ @@ -39,6 +39,7 @@ class JHInfoRow extends StatelessWidget { child: JHDisplayText( text: dataOne.title, fontSize: 12, + textAlign: TextAlign.center, style: TextStyle( color: Colors.grey.shade500, ), diff --git a/lib/common_widgets/jh_internet_connection_widget.dart b/lib/widgets/jh_internet_connection_widget.dart similarity index 100% rename from lib/common_widgets/jh_internet_connection_widget.dart rename to lib/widgets/jh_internet_connection_widget.dart diff --git a/lib/widgets/jh_line_chart.dart b/lib/widgets/jh_line_chart.dart new file mode 100644 index 0000000..c34661b --- /dev/null +++ b/lib/widgets/jh_line_chart.dart @@ -0,0 +1,309 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:times_up_flutter/app/helpers/parsing_extension.dart'; +import 'package:times_up_flutter/models/child_model/child_model.dart'; +import 'package:times_up_flutter/theme/theme.dart'; + +class JHLineChart extends StatelessWidget { + const JHLineChart({required this.model, Key? key}) : super(key: key); + final ChildModel model; + @override + Widget build(BuildContext context) { + return LineChart( + sampleData2, + swapAnimationDuration: const Duration(milliseconds: 250), + ); + } + + LineChartData get sampleData2 => LineChartData( + lineTouchData: lineTouchData2, + gridData: gridData, + titlesData: titlesData2, + borderData: borderData, + lineBarsData: lineBarsData2, + minX: 0, + maxX: 14, + maxY: 6, + minY: 0, + ); + + FlTitlesData get titlesData1 => FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: bottomTitles, + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + leftTitles: AxisTitles( + sideTitles: leftTitles(), + ), + ); + + LineTouchData get lineTouchData2 => LineTouchData( + enabled: false, + ); + + FlTitlesData get titlesData2 => FlTitlesData( + bottomTitles: AxisTitles( + sideTitles: bottomTitles, + ), + rightTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + leftTitles: AxisTitles( + sideTitles: leftTitles(), + ), + ); + + List get lineBarsData2 => [ + lineChartBarData2_1, + lineChartBarData2_2, + lineChartBarData2_3, + ]; + + Widget leftTitleWidgets(double value, TitleMeta meta) { + const style = TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14, + ); + String text; + switch (value.toInt()) { + case 1: + text = '1m'; + break; + case 2: + text = '2m'; + break; + case 3: + text = '3m'; + break; + case 4: + text = '5m'; + break; + case 5: + text = '6m'; + break; + default: + return Container(); + } + + return Text(text, style: style, textAlign: TextAlign.center); + } + + SideTitles leftTitles() => SideTitles( + getTitlesWidget: leftTitleWidgets, + showTitles: true, + interval: 1, + reservedSize: 40, + ); + + Widget bottomTitleWidgets(double value, TitleMeta meta) { + Widget text; + + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } + switch (value.toInt()) { + case 2: + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } else { + text = Image.memory( + model.appsUsageModel[getRandom(model.appsUsageModel.length - 1)] + .appIcon!, + height: 25, + ); + } + break; + case 4: + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } else { + text = Image.memory( + model.appsUsageModel[getRandom(model.appsUsageModel.length - 1)] + .appIcon!, + height: 25, + ); + } + break; + case 6: + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } else { + text = Image.memory( + model.appsUsageModel[getRandom(model.appsUsageModel.length - 1)] + .appIcon!, + height: 25, + ); + } + break; + case 8: + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } else { + text = Image.memory( + model.appsUsageModel[getRandom(model.appsUsageModel.length - 1)] + .appIcon!, + height: 25, + ); + } + break; + case 10: + if (model.appsUsageModel.isEmpty) { + text = const Text('0'); + } else { + text = Image.memory( + model.appsUsageModel[getRandom(model.appsUsageModel.length - 1)] + .appIcon!, + height: 25, + ); + } + break; + default: + text = const Text(''); + break; + } + + return SideTitleWidget( + axisSide: meta.axisSide, + space: 10, + child: text, + ); + } + + SideTitles get bottomTitles => SideTitles( + showTitles: true, + reservedSize: 32, + interval: 1, + getTitlesWidget: bottomTitleWidgets, + ); + + FlGridData get gridData => FlGridData(show: false); + + FlBorderData get borderData => FlBorderData( + show: true, + border: Border( + bottom: BorderSide( + color: CustomColors.indigoDark.withOpacity(0.2), width: 4,), + left: const BorderSide(color: Colors.transparent), + right: const BorderSide(color: Colors.transparent), + top: const BorderSide(color: Colors.transparent), + ), + ); + + LineChartBarData get lineChartBarData1_1 => LineChartBarData( + isCurved: true, + color: CustomColors.indigoDark, + barWidth: 8, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData(show: false), + spots: const [ + FlSpot(1, 1), + FlSpot(3, 1.5), + FlSpot(5, 1.4), + FlSpot(7, 3.4), + FlSpot(10, 2), + FlSpot(12, 2.2), + FlSpot(13, 1.8), + ], + ); + + LineChartBarData get lineChartBarData1_2 => LineChartBarData( + isCurved: true, + color: CustomColors.indigoDark, + barWidth: 8, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData( + show: false, + color: CustomColors.indigoDark.withOpacity(0), + ), + spots: const [ + FlSpot(1, 1), + FlSpot(3, 2.8), + FlSpot(7, 1.2), + FlSpot(10, 2.8), + FlSpot(12, 2.6), + FlSpot(13, 3.9), + ], + ); + + LineChartBarData get lineChartBarData1_3 => LineChartBarData( + isCurved: true, + color: CustomColors.greenPrimary, + barWidth: 8, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData(show: false), + spots: const [ + FlSpot(1, 2.8), + FlSpot(3, 1.9), + FlSpot(6, 3), + FlSpot(10, 1.3), + FlSpot(13, 2.5), + ], + ); + + LineChartBarData get lineChartBarData2_1 => LineChartBarData( + isCurved: true, + curveSmoothness: 0, + color: CustomColors.greenPrimary.withOpacity(0.5), + barWidth: 4, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData(show: false), + spots: const [ + FlSpot(1, 1), + FlSpot(3, 4), + FlSpot(5, 1.8), + FlSpot(7, 5), + FlSpot(10, 2), + FlSpot(12, 2.2), + FlSpot(13, 1.8), + ], + ); + + LineChartBarData get lineChartBarData2_2 => LineChartBarData( + isCurved: true, + color: CustomColors.indigoDark.withOpacity(0.5), + barWidth: 4, + isStrokeCapRound: true, + dotData: FlDotData(show: false), + belowBarData: BarAreaData( + show: true, + color: CustomColors.indigoDark.withOpacity(0.2), + ), + spots: const [ + FlSpot(1, 1), + FlSpot(3, 2.8), + FlSpot(7, 1.2), + FlSpot(10, 2.8), + FlSpot(12, 2.6), + FlSpot(13, 3.9), + ], + ); + + LineChartBarData get lineChartBarData2_3 => LineChartBarData( + isCurved: true, + curveSmoothness: 0, + color: CustomColors.indigoDark.withOpacity(0.5), + barWidth: 2, + isStrokeCapRound: true, + dotData: FlDotData(show: true), + belowBarData: BarAreaData(show: false), + spots: const [ + FlSpot(1, 3.8), + FlSpot(3, 1.9), + FlSpot(6, 5), + FlSpot(10, 3.3), + FlSpot(13, 4.5), + ], + ); +} diff --git a/lib/common_widgets/jh_loading_widget.dart b/lib/widgets/jh_loading_widget.dart similarity index 100% rename from lib/common_widgets/jh_loading_widget.dart rename to lib/widgets/jh_loading_widget.dart diff --git a/lib/common_widgets/jh_no_implementation.dart b/lib/widgets/jh_no_implementation.dart similarity index 57% rename from lib/common_widgets/jh_no_implementation.dart rename to lib/widgets/jh_no_implementation.dart index 27ab4c2..65ba96c 100644 --- a/lib/common_widgets/jh_no_implementation.dart +++ b/lib/widgets/jh_no_implementation.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:line_awesome_flutter/line_awesome_flutter.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; class JHNoImplementationWidget extends StatelessWidget { const JHNoImplementationWidget({ @@ -29,19 +28,38 @@ class JHNoImplementationWidget extends StatelessWidget { child: Center( child: Column( children: [ - const Icon(LineAwesomeIcons.info), - JHDisplayText( - text: title ?? 'Oops :( ', - style: const TextStyle(fontWeight: FontWeight.w500), + Row( + children: [ + const Spacer(), + JHDisplayText( + text: title ?? 'Oops ', + style: const TextStyle(fontWeight: FontWeight.w500), + ), + const Icon(Icons.error), + const Spacer(), + ], ), const SizedBox(height: 8), + JHDisplayText( + text: content ?? 'Not implemented yet.', + fontSize: 17, + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, + fontWeight: FontWeight.w700, + ), + ).vP16, JHDisplayText( text: content ?? - 'Not implemented yet. We are actively working to' + 'We are actively working to' ' implement ' ' that we will let you know soon.', - style: const TextStyle( - color: Colors.grey, + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).brightness == Brightness.dark + ? Colors.white + : CustomColors.indigoDark, fontWeight: FontWeight.w400, ), ).vP16, diff --git a/lib/common_widgets/jh_pin_marker.dart b/lib/widgets/jh_pin_marker.dart similarity index 100% rename from lib/common_widgets/jh_pin_marker.dart rename to lib/widgets/jh_pin_marker.dart diff --git a/lib/common_widgets/jh_progress_bar.dart b/lib/widgets/jh_progress_bar.dart similarity index 100% rename from lib/common_widgets/jh_progress_bar.dart rename to lib/widgets/jh_progress_bar.dart diff --git a/lib/widgets/jh_shimmer_map.dart b/lib/widgets/jh_shimmer_map.dart new file mode 100644 index 0000000..a6d9190 --- /dev/null +++ b/lib/widgets/jh_shimmer_map.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:shimmer/shimmer.dart'; + +class ShimmerMap extends StatelessWidget { + const ShimmerMap({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Shimmer.fromColors( + baseColor: Theme.of(context).scaffoldBackgroundColor, + highlightColor: Theme.of(context).scaffoldBackgroundColor, + child: Container( + color: Theme.of(context).scaffoldBackgroundColor, + width: double.infinity, + height: MediaQuery.of(context).size.height, + ), + ); + } +} diff --git a/lib/common_widgets/jh_size_config.dart b/lib/widgets/jh_size_config.dart similarity index 100% rename from lib/common_widgets/jh_size_config.dart rename to lib/widgets/jh_size_config.dart diff --git a/lib/common_widgets/jh_summary_tile.dart b/lib/widgets/jh_summary_tile.dart similarity index 86% rename from lib/common_widgets/jh_summary_tile.dart rename to lib/widgets/jh_summary_tile.dart index 89457af..74e9438 100644 --- a/lib/common_widgets/jh_summary_tile.dart +++ b/lib/widgets/jh_summary_tile.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; -import 'package:times_up_flutter/common_widgets/jh_progress_bar.dart'; -import 'package:times_up_flutter/common_widgets/jh_size_config.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_progress_bar.dart'; +import 'package:times_up_flutter/widgets/jh_size_config.dart'; class JHSummaryTile extends StatelessWidget { const JHSummaryTile({ diff --git a/lib/common_widgets/show_alert_dialog.dart b/lib/widgets/show_alert_dialog.dart similarity index 93% rename from lib/common_widgets/show_alert_dialog.dart rename to lib/widgets/show_alert_dialog.dart index ee461ee..14fb74e 100644 --- a/lib/common_widgets/show_alert_dialog.dart +++ b/lib/widgets/show_alert_dialog.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:line_awesome_flutter/line_awesome_flutter.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; import 'package:times_up_flutter/theme/theme.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; Future showAlertDialog( BuildContext context, { diff --git a/lib/common_widgets/show_bottom_sheet.dart b/lib/widgets/show_bottom_sheet.dart similarity index 100% rename from lib/common_widgets/show_bottom_sheet.dart rename to lib/widgets/show_bottom_sheet.dart diff --git a/lib/common_widgets/show_exeption_alert.dart b/lib/widgets/show_exeption_alert.dart similarity index 87% rename from lib/common_widgets/show_exeption_alert.dart rename to lib/widgets/show_exeption_alert.dart index 48bc36e..3725a80 100644 --- a/lib/common_widgets/show_exeption_alert.dart +++ b/lib/widgets/show_exeption_alert.dart @@ -1,6 +1,6 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; -import 'package:times_up_flutter/common_widgets/show_alert_dialog.dart'; +import 'package:times_up_flutter/widgets/show_alert_dialog.dart'; Future showExceptionAlertDialog( BuildContext context, { diff --git a/lib/common_widgets/show_logger.dart b/lib/widgets/show_logger.dart similarity index 100% rename from lib/common_widgets/show_logger.dart rename to lib/widgets/show_logger.dart diff --git a/pubspec.lock b/pubspec.lock index e738d9f..4aadf5d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -269,10 +269,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" dart_style: dependency: transitive description: @@ -494,6 +494,38 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_background_service: + dependency: "direct main" + description: + name: flutter_background_service + sha256: "7b18ac89f88e521a44e9da8fcaa768a59b7c2cfad9f41bf3fcc6cf673032e33e" + url: "https://pub.dev" + source: hosted + version: "5.0.2" + flutter_background_service_android: + dependency: transitive + description: + name: flutter_background_service_android + sha256: "4998b3d191a04f36f720eff69d3905f384b91e1f92b3dd74aca4ffb5670f38dc" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + flutter_background_service_ios: + dependency: transitive + description: + name: flutter_background_service_ios + sha256: ab73657535876e16abc89e40f924df3e92ad3dee83f64d187081417e824709ed + url: "https://pub.dev" + source: hosted + version: "5.0.0" + flutter_background_service_platform_interface: + dependency: transitive + description: + name: flutter_background_service_platform_interface + sha256: cd5720ff5b051d551a4734fae16683aace779bd0425e8d3f15d84a0cdcc2d8d9 + url: "https://pub.dev" + source: hosted + version: "5.0.0" flutter_bloc: dependency: "direct main" description: @@ -1017,10 +1049,10 @@ packages: dependency: "direct main" description: name: mockito - sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 + sha256: "7d5b53bcd556c1bc7ffbe4e4d5a19c3e112b7e925e9e172dd7c6ad0630812616" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.2" mocktail: dependency: "direct dev" description: @@ -1333,6 +1365,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.3" + shimmer: + dependency: "direct main" + description: + name: shimmer + sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf" + url: "https://pub.dev" + source: hosted + version: "2.0.0" showcaseview: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index ed955fa..1d60b15 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: # Notification Packages: firebase_messaging: ^14.4.0 flutter_local_notifications: ^14.0.0-dev.2 + flutter_background_service: ^5.0.2 # Utility Packages: intl: any @@ -54,6 +55,7 @@ dependencies: # UI Component Packages: line_awesome_flutter: ^2.0.0 showcaseview: ^2.0.3 + shimmer: ^2.0.0 # Testing Packages: @@ -69,7 +71,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.0 + cupertino_icons: ^1.0.6 freezed_annotation: ^2.2.0 json_annotation: ^4.8.1 diff --git a/test/app/config/geo_full_test.dart b/test/app/config/geo_full_test.dart index e6306bf..4755466 100644 --- a/test/app/config/geo_full_test.dart +++ b/test/app/config/geo_full_test.dart @@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:geolocator/geolocator.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/app/config/geo_full.dart'; +import 'package:times_up_flutter/app/features/parent_side/map_page.dart'; import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; import 'package:times_up_flutter/utils/constants.dart'; @@ -34,7 +34,7 @@ void main() { child: Provider( create: (_) => mockAuthBase, builder: (context, __) => MaterialApp( - home: GeoFull.create( + home: MapView.create( context, position: position, database: mockDatabase, @@ -60,7 +60,7 @@ void main() { child: Provider( create: (_) => mockAuthBase, builder: (context, __) => MaterialApp( - home: GeoFull.create( + home: MapView.create( context, position: position, database: mockDatabase, diff --git a/test/app/landing_page_test.dart b/test/app/landing_page_test.dart index 2daa583..20fdfbc 100644 --- a/test/app/landing_page_test.dart +++ b/test/app/landing_page_test.dart @@ -4,9 +4,9 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:times_up_flutter/app/pages/child_page.dart'; -import 'package:times_up_flutter/app/pages/parent_page.dart'; -import 'package:times_up_flutter/sign_in/sign_in_page.dart'; +import 'package:times_up_flutter/app/features/child_side/child_page.dart'; +import 'package:times_up_flutter/app/features/parent_side/parent_page.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_page.dart'; import '../helpers/test_helpers.dart'; import '../helpers/test_helpers.mocks.dart'; diff --git a/test/helpers/test_helpers.dart b/test/helpers/test_helpers.dart index 8d937f7..f6c9a53 100644 --- a/test/helpers/test_helpers.dart +++ b/test/helpers/test_helpers.dart @@ -3,14 +3,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:provider/provider.dart'; -import 'package:times_up_flutter/app/landing_page.dart'; +import 'package:times_up_flutter/app/features/landing_page.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_bloc.dart'; import 'package:times_up_flutter/services/app_usage_local_service.dart'; import 'package:times_up_flutter/services/app_usage_service.dart'; import 'package:times_up_flutter/services/auth.dart'; import 'package:times_up_flutter/services/database.dart'; import 'package:times_up_flutter/services/geo_locator_service.dart'; import 'package:times_up_flutter/services/notification_service.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_bloc.dart'; import 'test_helpers.mocks.dart'; diff --git a/test/helpers/test_helpers.mocks.dart b/test/helpers/test_helpers.mocks.dart index b52f85a..609286f 100644 --- a/test/helpers/test_helpers.mocks.dart +++ b/test/helpers/test_helpers.mocks.dart @@ -1,4 +1,4 @@ -// Mocks generated by Mockito 5.4.0 from annotations +// Mocks generated by Mockito 5.4.2 from annotations // in times_up_flutter/test/helpers/test_helpers.dart. // Do not manually edit this file. @@ -17,6 +17,10 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart' import 'package:geolocator/geolocator.dart' as _i4; import 'package:installed_apps/app_info.dart' as _i21; import 'package:mockito/mockito.dart' as _i1; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_bloc.dart' + as _i17; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_model.dart' + as _i18; import 'package:times_up_flutter/models/child_model/child_model.dart' as _i3; import 'package:times_up_flutter/models/email_model.dart' as _i13; import 'package:times_up_flutter/models/notification_model/notification_model.dart' @@ -27,8 +31,6 @@ import 'package:times_up_flutter/services/auth.dart' as _i5; import 'package:times_up_flutter/services/database.dart' as _i10; import 'package:times_up_flutter/services/geo_locator_service.dart' as _i6; import 'package:times_up_flutter/services/notification_service.dart' as _i20; -import 'package:times_up_flutter/sign_in/email_sign_in_bloc.dart' as _i17; -import 'package:times_up_flutter/sign_in/email_sign_in_model.dart' as _i18; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -379,7 +381,6 @@ class MockDatabase extends _i1.Mock implements _i10.Database { @override _i9.Future liveUpdateChild( _i3.ChildModel? model, - int? tick, _i11.AppUsageService? apps, ) => (super.noSuchMethod( @@ -387,7 +388,6 @@ class MockDatabase extends _i1.Mock implements _i10.Database { #liveUpdateChild, [ model, - tick, apps, ], ), @@ -1246,18 +1246,6 @@ class MockFireStoreDatabase extends _i1.Mock implements _i10.FireStoreDatabase { returnValueForMissingStub: null, ); @override - _i3.ChildModel get currentChild => (super.noSuchMethod( - Invocation.getter(#currentChild), - returnValue: _FakeChildModel_1( - this, - Invocation.getter(#currentChild), - ), - returnValueForMissingStub: _FakeChildModel_1( - this, - Invocation.getter(#currentChild), - ), - ) as _i3.ChildModel); - @override _i9.Future setChild(_i3.ChildModel? model) => (super.noSuchMethod( Invocation.method( #setChild, @@ -1366,7 +1354,6 @@ class MockFireStoreDatabase extends _i1.Mock implements _i10.FireStoreDatabase { @override _i9.Future liveUpdateChild( _i3.ChildModel? model, - int? value, _i11.AppUsageService? apps, ) => (super.noSuchMethod( @@ -1374,7 +1361,6 @@ class MockFireStoreDatabase extends _i1.Mock implements _i10.FireStoreDatabase { #liveUpdateChild, [ model, - value, apps, ], ), diff --git a/test/models/set_child_model_test.dart b/test/models/set_child_model_test.dart deleted file mode 100644 index 1750130..0000000 --- a/test/models/set_child_model_test.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:times_up_flutter/models/set_child_model.dart'; - -void main() { - test('SetChildModel Constructor', () { - // Test values - const name = 'John Doe'; - const email = 'john.doe@example.com'; - - // Create the SetChildModel instance - final setChildModel = SetChildModel( - name: name, - email: email, - ); - - // Verify the values - expect(setChildModel.name, name); - expect(setChildModel.email, email); - }); -} diff --git a/test/services/firestore_service_test.dart b/test/services/firestore_service_test.dart index 2a9d71e..b717826 100644 --- a/test/services/firestore_service_test.dart +++ b/test/services/firestore_service_test.dart @@ -7,16 +7,13 @@ import '../mocks.dart'; void main() { test('setChild', () async { final database = MockDatabase(); - // Replace with your ChildModel instance await database.setChild(Dummy.childModel); }); test('liveUpdateChild', () async { final database = MockDatabase(); final usage = AppUsageService(); - // Replace with your ChildModel instance - const tick = 5; // Replace with your tick value - await database.liveUpdateChild(Dummy.childModel, tick, usage); + await database.liveUpdateChild(Dummy.childModel, usage); }); } diff --git a/test/sign-in/email_sign_in_bloc_test.dart b/test/sign-in/email_sign_in_bloc_test.dart index 1781855..80249a8 100644 --- a/test/sign-in/email_sign_in_bloc_test.dart +++ b/test/sign-in/email_sign_in_bloc_test.dart @@ -1,8 +1,8 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_bloc.dart'; -import 'package:times_up_flutter/sign_in/email_sign_in_model.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_bloc.dart'; +import 'package:times_up_flutter/app/features/sign_in/email_sign_in_model.dart'; import '../helpers/test_helpers.mocks.dart'; diff --git a/test/sign-in/sign_in_manager_test.dart b/test/sign-in/sign_in_manager_test.dart index 67c2f5e..88452f9 100644 --- a/test/sign-in/sign_in_manager_test.dart +++ b/test/sign-in/sign_in_manager_test.dart @@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:times_up_flutter/sign_in/sign_in_manager.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_manager.dart'; import '../helpers/test_helpers.mocks.dart'; diff --git a/test/sign-in/sign_in_page_test.dart b/test/sign-in/sign_in_page_test.dart index 8eb1e30..98dca9e 100644 --- a/test/sign-in/sign_in_page_test.dart +++ b/test/sign-in/sign_in_page_test.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; import 'package:provider/provider.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_button.dart'; +import 'package:times_up_flutter/app/features/sign_in/sign_in_page.dart'; import 'package:times_up_flutter/services/auth.dart'; -import 'package:times_up_flutter/sign_in/sign_in_button.dart'; -import 'package:times_up_flutter/sign_in/sign_in_page.dart'; import '../helpers/test_helpers.mocks.dart'; diff --git a/test/validators_test.dart b/test/validators_test.dart index a60e27b..450e605 100644 --- a/test/validators_test.dart +++ b/test/validators_test.dart @@ -1,31 +1,19 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:times_up_flutter/sign_in/validators.dart'; +import 'package:times_up_flutter/app/features/sign_in/validators.dart'; void main() { test('non empty string ', () { final validator = NonEmptyStringValidator(); - - ///here we pass a string [test] to check if the method isValid will - /// return true - /// expect(validator.isValid('test'), true); }); test('empty string ', () { final validator = NonEmptyStringValidator(); - - ///here we pass a string [test] to check if the method isValid will - ///return false - /// when the string is empty expect(validator.isValid(''), false); }); test('null string ', () { final validator = NonEmptyStringValidator(); - - ///here we pass a string [test] to check if the method isValid will - ///return null - /// when the string is empty expect(validator.isValid(null), false); }); } diff --git a/test/common_widgets/auto_size_text_test.dart b/test/widgets/auto_size_text_test.dart similarity index 91% rename from test/common_widgets/auto_size_text_test.dart rename to test/widgets/auto_size_text_test.dart index 0b94b2c..44b525b 100644 --- a/test/common_widgets/auto_size_text_test.dart +++ b/test/widgets/auto_size_text_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:times_up_flutter/common_widgets/jh_display_text.dart'; +import 'package:times_up_flutter/widgets/jh_display_text.dart'; import '../helpers/test_helpers.dart'; diff --git a/test/common_widgets/bar_chart_test.dart b/test/widgets/bar_chart_test.dart similarity index 100% rename from test/common_widgets/bar_chart_test.dart rename to test/widgets/bar_chart_test.dart diff --git a/test/common_widgets/custom_raised_button_test.dart b/test/widgets/custom_raised_button_test.dart similarity index 94% rename from test/common_widgets/custom_raised_button_test.dart rename to test/widgets/custom_raised_button_test.dart index ac6f9cc..fbadddc 100644 --- a/test/common_widgets/custom_raised_button_test.dart +++ b/test/widgets/custom_raised_button_test.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:times_up_flutter/common_widgets/jh_custom_raised_button.dart'; +import 'package:times_up_flutter/widgets/jh_custom_raised_button.dart'; void main() { /// This syntax is used to test widgets diff --git a/test/common_widgets/empty_content_test.dart b/test/widgets/empty_content_test.dart similarity index 88% rename from test/common_widgets/empty_content_test.dart rename to test/widgets/empty_content_test.dart index b2dfcbf..39201e8 100644 --- a/test/common_widgets/empty_content_test.dart +++ b/test/widgets/empty_content_test.dart @@ -1,5 +1,5 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:times_up_flutter/common_widgets/jh_empty_content.dart'; +import 'package:times_up_flutter/widgets/jh_empty_content.dart'; import '../helpers/test_helpers.dart'; diff --git a/test/common_widgets/feature_widget_test.dart b/test/widgets/feature_widget_test.dart similarity index 100% rename from test/common_widgets/feature_widget_test.dart rename to test/widgets/feature_widget_test.dart diff --git a/test/common_widgets/loading_map_test.dart b/test/widgets/loading_map_test.dart similarity index 100% rename from test/common_widgets/loading_map_test.dart rename to test/widgets/loading_map_test.dart diff --git a/test/common_widgets/show_alert_dialog.dart b/test/widgets/show_alert_dialog.dart similarity index 100% rename from test/common_widgets/show_alert_dialog.dart rename to test/widgets/show_alert_dialog.dart