From 70e3ed89788f27687d781bb1ad74259657652441 Mon Sep 17 00:00:00 2001 From: anhtunz Date: Thu, 26 Dec 2024 14:52:33 +0700 Subject: [PATCH] refactor(theme&ui): improve theme switching and navigation styling --- lib/feature/main/main_bloc.dart | 5 + lib/feature/main/main_screen.dart | 400 +++++++++---------- lib/main.dart | 45 ++- lib/product/services/theme_services.dart | 33 ++ lib/product/theme/provider/app_provider.dart | 23 -- lib/product/theme/theme_notifier.dart | 115 ------ 6 files changed, 255 insertions(+), 366 deletions(-) create mode 100644 lib/product/services/theme_services.dart delete mode 100644 lib/product/theme/provider/app_provider.dart delete mode 100644 lib/product/theme/theme_notifier.dart diff --git a/lib/feature/main/main_bloc.dart b/lib/feature/main/main_bloc.dart index 3cea41d..74b415a 100644 --- a/lib/feature/main/main_bloc.dart +++ b/lib/feature/main/main_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:sfm_app/product/base/bloc/base_bloc.dart'; +import 'package:sfm_app/product/constant/enums/app_theme_enums.dart'; import '../bell/bell_model.dart'; @@ -14,6 +15,10 @@ class MainBloc extends BlocBase { final language = StreamController.broadcast(); StreamSink get sinkLanguage => language.sink; Stream get streamLanguage => language.stream; + + final theme = StreamController.broadcast(); + StreamSink get sinkTheme => theme.sink; + Stream get streamTheme => theme.stream; final themeMode = StreamController.broadcast(); StreamSink get sinkThemeMode => themeMode.sink; diff --git a/lib/feature/main/main_screen.dart b/lib/feature/main/main_screen.dart index 02ff8aa..0753dff 100644 --- a/lib/feature/main/main_screen.dart +++ b/lib/feature/main/main_screen.dart @@ -6,15 +6,12 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.dart'; -import 'package:provider/provider.dart'; import 'package:badges/badges.dart' as badges; -import 'package:sfm_app/feature/home/home_bloc.dart'; -import 'package:sfm_app/product/constant/app/app_constants.dart'; -import 'package:sfm_app/product/constant/enums/app_route_enums.dart'; -import 'package:sfm_app/product/constant/enums/role_enums.dart'; -import 'package:sfm_app/product/permission/location_permission.dart'; -import 'package:sfm_app/product/shared/shared_language_switch.dart'; -import '../../product/shared/shared_light_dark_switch.dart'; +import '../home/home_bloc.dart'; +import '../../product/constant/app/app_constants.dart'; +import '../../product/constant/enums/app_route_enums.dart'; +import '../../product/permission/location_permission.dart'; +import '../../product/services/theme_services.dart'; import '../devices/devices_manager_bloc.dart'; import '../devices/devices_manager_screen.dart'; import '../home/home_screen.dart'; @@ -27,13 +24,11 @@ import '../map/map_bloc.dart'; import '../map/map_screen.dart'; import '../../product/base/bloc/base_bloc.dart'; import '../../product/constant/enums/app_theme_enums.dart'; -import '../../product/extention/context_extention.dart'; import '../../product/services/api_services.dart'; import '../../main.dart'; import '../../product/constant/icon/icon_constants.dart'; import '../../product/constant/lang/language_constants.dart'; import '../../product/services/language_services.dart'; -import '../../product/theme/theme_notifier.dart'; import '../bell/bell_model.dart'; class MainScreen extends StatefulWidget { @@ -69,16 +64,12 @@ class _MainScreenState extends State with WidgetsBindingObserver { } mainBloc.sinkIsVNIcon.add(isVN); mainBloc.sinkThemeMode.add(isLight); - log("role: $role"); LocationPermissionRequest.instance.checkLocationPermission(context); } -// For test - late bool dayNightToggle2; @override void initState() { super.initState(); - dayNightToggle2 = false; mainBloc = BlocProvider.of(context); WidgetsBinding.instance.addObserver(this); initialCheck(); @@ -178,216 +169,199 @@ class _MainScreenState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - ThemeNotifier themeNotifier = context.watch(); - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - actions: [ - // LightDarkSwitch( - // value: !isLight, - // onChanged: (value) { - // themeNotifier.changeTheme(); - // isLight = !isLight; - // }, - // ), - // SizedBox( - // width: context.lowValue, - // ), - // StreamBuilder( - // stream: mainBloc.streamIsVNIcon, - // builder: (context, isVNSnapshot) { - // return LanguageSwitch( - // value: isVNSnapshot.data ?? isVN, - // onChanged: (value) async { - // Locale locale = await LanguageServices().setLocale(isVN - // ? LanguageConstants.ENGLISH - // : LanguageConstants.VIETNAM); - // MyApp.setLocale(context, locale); - // isVN = !isVN; - // mainBloc.sinkIsVNIcon.add(isVN); - // }, - // ); - // }), - // SizedBox( - // width: context.lowValue, - // ), - StreamBuilder( - stream: mainBloc.streamThemeMode, - initialData: isLight, - builder: (context, themeModeSnapshot) { - return IconButton( - onPressed: () { - themeNotifier.changeTheme(); + return StreamBuilder( + stream: mainBloc.streamThemeMode, + initialData: isLight, + builder: (context, themeModeSnapshot) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + actions: [ + // LightDarkSwitch( + // value: !isLight, + // onChanged: (value) { + // themeNotifier.changeTheme(); + // isLight = !isLight; + // }, + // ), + // SizedBox( + // width: context.lowValue, + // ), + // StreamBuilder( + // stream: mainBloc.streamIsVNIcon, + // builder: (context, isVNSnapshot) { + // return LanguageSwitch( + // value: isVNSnapshot.data ?? isVN, + // onChanged: (value) async { + // Locale locale = await LanguageServices().setLocale(isVN + // ? LanguageConstants.ENGLISH + // : LanguageConstants.VIETNAM); + // MyApp.setLocale(context, locale); + // isVN = !isVN; + // mainBloc.sinkIsVNIcon.add(isVN); + // }, + // ); + // }), + // SizedBox( + // width: context.lowValue, + // ), + IconButton( + onPressed: () async { + ThemeData newTheme = await ThemeServices().changeTheme( + isLight ? AppThemes.DARK.name : AppThemes.LIGHT.name); + MyApp.setTheme(context, newTheme); isLight = !isLight; mainBloc.sinkThemeMode.add(isLight); }, icon: Icon( themeModeSnapshot.data ?? isLight - ? Icons.light_mode_outlined - : Icons.dark_mode_outlined, + ? Icons.dark_mode_outlined + : Icons.light_mode_outlined, ), - ); - }, - ), - StreamBuilder( - stream: mainBloc.streamIsVNIcon, - initialData: isVN, - builder: (context, isVnSnapshot) { - return IconButton( - onPressed: () async { - log("Locale: ${LanguageServices().getLocale()}"); - Locale locale = await LanguageServices().setLocale(isVN - ? LanguageConstants.ENGLISH - : LanguageConstants.VIETNAM); - MyApp.setLocale(context, locale); - isVN = !isVN; - mainBloc.sinkIsVNIcon.add(isVN); + ), + StreamBuilder( + stream: mainBloc.streamIsVNIcon, + initialData: isVN, + builder: (context, isVnSnapshot) { + return IconButton( + onPressed: () async { + log("Locale: ${LanguageServices().getLocale()}"); + Locale locale = await LanguageServices().setLocale(isVN + ? LanguageConstants.ENGLISH + : LanguageConstants.VIETNAM); + MyApp.setLocale(context, locale); + isVN = !isVN; + mainBloc.sinkIsVNIcon.add(isVN); + }, + icon: Image.asset( + IconConstants.instance.getIcon( + isVnSnapshot.data ?? isVN ? 'vi_icon' : 'en_icon'), + height: 24, + width: 24, + ), + ); }, - icon: Image.asset( - IconConstants.instance.getIcon( - isVnSnapshot.data ?? isVN ? 'vi_icon' : 'en_icon'), - height: 24, - width: 24, + ), + StreamBuilder( + stream: mainBloc.streamBellBloc, + builder: (context, bellSnapshot) { + return checkStatus(bellSnapshot.data?.items ?? []) + ? IconButton( + onPressed: () { + context.pushNamed(AppRoutes.BELL.name); + }, + icon: const Icon( + Icons.notifications, + ), + ) + : GestureDetector( + child: badges.Badge( + badgeStyle: const badges.BadgeStyle( + shape: badges.BadgeShape.twitter, + ), + key: _badgeKey, + badgeContent: const Icon( + CupertinoIcons.circle_filled, + color: Colors.red, + size: 5, + ), + badgeAnimation: const badges.BadgeAnimation.slide( + animationDuration: Duration(milliseconds: 200), + colorChangeAnimationDuration: + Duration(seconds: 1), + loopAnimation: false, + curve: Curves.decelerate, + colorChangeAnimationCurve: Curves.easeInCirc, + ), + showBadge: true, + // ignorePointer: false, + child: const Icon( + Icons.notifications, + size: 30, + ), + ), + onTap: () { + context.pushNamed(AppRoutes.BELL.name); + }, + ); + }, + ), + PopupMenuButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), ), - ); - }, - ), - StreamBuilder( - stream: mainBloc.streamBellBloc, - builder: (context, bellSnapshot) { - return checkStatus(bellSnapshot.data?.items ?? []) - ? IconButton( - onPressed: () { - context.pushNamed(AppRoutes.BELL.name); - }, - icon: const Icon( - Icons.notifications, - ), - ) - : GestureDetector( - child: badges.Badge( - badgeStyle: const badges.BadgeStyle( - shape: badges.BadgeShape.twitter, - ), - key: _badgeKey, - badgeContent: const Icon( - CupertinoIcons.circle_filled, - color: Colors.red, - size: 5, - ), - badgeAnimation: const badges.BadgeAnimation.slide( - animationDuration: Duration(milliseconds: 200), - colorChangeAnimationDuration: Duration(seconds: 1), - loopAnimation: false, - curve: Curves.decelerate, - colorChangeAnimationCurve: Curves.easeInCirc, - ), - showBadge: true, - // ignorePointer: false, - child: const Icon( - Icons.notifications, - size: 30, - ), - ), + icon: const Icon(Icons.more_vert), + itemBuilder: (context) { + return [ + PopupMenuItem( + value: ApplicationConstants.SETTINGS_PATH, onTap: () { - context.pushNamed(AppRoutes.BELL.name); + context.pushNamed(AppRoutes.SETTINGS.name); }, - ); - }, - ), - PopupMenuButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0), - ), - icon: const Icon(Icons.more_vert), - itemBuilder: (context) { - return [ - PopupMenuItem( - value: ApplicationConstants.SETTINGS_PATH, - onTap: () { - context.pushNamed(AppRoutes.SETTINGS.name); - }, - child: Row( - children: [ - const Icon(Icons.person), - const SizedBox(width: 5), - Text(appLocalization(context).profile_icon_title) - ], - ), - ), - PopupMenuItem( - value: ApplicationConstants.LOGOUT_PATH, - onTap: () { - Future.delayed( - const Duration(milliseconds: 200), - () async { - await apiServices.logOut(context); - }, - ); - }, - child: Row( - children: [ - const Icon(Icons.logout), - const SizedBox(width: 5), - Text( - appLocalization(context).log_out, + child: Row( + children: [ + const Icon(Icons.person), + const SizedBox(width: 5), + Text(appLocalization(context).profile_icon_title) + ], ), - ], - ), - ), - ]; - }, - ) - ], - ), - // bottomNavigationBar: Container( - // decoration: - // BoxDecoration(borderRadius: BorderRadius.circular(50)), - // padding: context.paddingLow, - // child: NavigationBar( - // onDestinationSelected: (index) { - // currentPageIndex = index; - // mainBloc.sinkCurrentPageIndex.add(currentPageIndex); - // checkSelectedIndex(currentPageIndex); - // }, - // selectedIndex: indexSnapshot.data ?? currentPageIndex, - // destinations: roleSnapshot.data == RoleEnums.USER.name - // ? userDestinations - // : modDestinations, - // ), - // ), - // body: IndexedStack( - // index: indexSnapshot.data ?? currentPageIndex, - // children: roleSnapshot.data == RoleEnums.USER.name - // ? userBody - // : modBody, - // ), - body: PersistentTabView( - context, - controller: controller, - screens: _buildScreens(), - items: _navBarsItems(), - confineInSafeArea: true, - handleAndroidBackButtonPress: true, - resizeToAvoidBottomInset: true, - stateManagement: true, - hideNavigationBarWhenKeyboardShows: true, - // backgroundColor: Colors.transparent, - decoration: NavBarDecoration( - borderRadius: BorderRadius.circular(30.0), - ), - popAllScreensOnTapOfSelectedTab: true, - itemAnimationProperties: const ItemAnimationProperties( - duration: Duration(milliseconds: 200), - curve: Curves.bounceInOut, - ), - screenTransitionAnimation: const ScreenTransitionAnimation( - animateTabTransition: true, - curve: Curves.linear, - duration: Duration(milliseconds: 200), - ), - navBarStyle: NavBarStyle.style4, - ), + ), + PopupMenuItem( + value: ApplicationConstants.LOGOUT_PATH, + onTap: () { + Future.delayed( + const Duration(milliseconds: 200), + () async { + await apiServices.logOut(context); + }, + ); + }, + child: Row( + children: [ + const Icon(Icons.logout), + const SizedBox(width: 5), + Text( + appLocalization(context).log_out, + ), + ], + ), + ), + ]; + }, + ) + ], + ), + body: PersistentTabView( + context, + controller: controller, + screens: _buildScreens(), + items: _navBarsItems(), + confineInSafeArea: true, + handleAndroidBackButtonPress: true, + resizeToAvoidBottomInset: true, + stateManagement: true, + hideNavigationBarWhenKeyboardShows: true, + backgroundColor: + themeModeSnapshot.data! ? Colors.white : Colors.black, + decoration: NavBarDecoration( + borderRadius: BorderRadius.circular(30.0), + colorBehindNavBar: + themeModeSnapshot.data! ? Colors.white : Colors.black, + ), + popAllScreensOnTapOfSelectedTab: true, + itemAnimationProperties: const ItemAnimationProperties( + duration: Duration(milliseconds: 200), + curve: Curves.bounceInOut, + ), + screenTransitionAnimation: const ScreenTransitionAnimation( + animateTabTransition: true, + curve: Curves.linear, + duration: Duration(milliseconds: 200), + ), + navBarStyle: NavBarStyle.style4, + ), + ); + }, ); } diff --git a/lib/main.dart b/lib/main.dart index 155aa19..1219cec 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,24 +1,19 @@ import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; +import 'package:sfm_app/product/services/theme_services.dart'; import 'product/services/language_services.dart'; import 'feature/main/main_bloc.dart'; import 'product/base/bloc/base_bloc.dart'; import 'product/constant/navigation/navigation_router.dart'; -import 'product/theme/provider/app_provider.dart'; -import 'product/theme/theme_notifier.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); runApp( - MultiProvider( - providers: [...ApplicationProvider.instance.dependItems], - child: BlocProvider( - child: const MyApp(), - blocBuilder: () => MainBloc(), - ), + BlocProvider( + child: const MyApp(), + blocBuilder: () => MainBloc(), ), ); } @@ -32,17 +27,30 @@ class MyApp extends StatefulWidget { _MyAppState? state = context.findAncestorStateOfType<_MyAppState>(); state?.setLocale(newLocale); } + + static void setTheme(BuildContext context, ThemeData newTheme) { + _MyAppState? state = context.findAncestorStateOfType<_MyAppState>(); + state?.setTheme(newTheme); + } } class _MyAppState extends State { Locale? _locale; + ThemeData? _themeData; late MainBloc mainBloc; LanguageServices languageServices = LanguageServices(); + ThemeServices themeServices = ThemeServices(); + setLocale(Locale locale) { _locale = locale; mainBloc.sinkLanguage.add(_locale); } + setTheme(ThemeData theme) { + _themeData = theme; + mainBloc.sinkTheme.add(_themeData); + } + @override void initState() { super.initState(); @@ -52,6 +60,7 @@ class _MyAppState extends State { @override void didChangeDependencies() { languageServices.getLocale().then((locale) => {setLocale(locale)}); + themeServices.getTheme().then((theme) => {setTheme(theme)}); super.didChangeDependencies(); } @@ -62,12 +71,18 @@ class _MyAppState extends State { stream: mainBloc.streamLanguage, initialData: _locale, builder: (context, languageSnapshot) { - return MaterialApp.router( - theme: context.watch().currentTheme, - routerConfig: router, - localizationsDelegates: AppLocalizations.localizationsDelegates, - supportedLocales: AppLocalizations.supportedLocales, - locale: languageSnapshot.data, + return StreamBuilder( + stream: mainBloc.streamTheme, + initialData: _themeData, + builder: (context, themeSnapshot) { + return MaterialApp.router( + theme: themeSnapshot.data, + routerConfig: router, + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + locale: languageSnapshot.data, + ); + } ); }, ); diff --git a/lib/product/services/theme_services.dart b/lib/product/services/theme_services.dart new file mode 100644 index 0000000..b78c402 --- /dev/null +++ b/lib/product/services/theme_services.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +import '../cache/local_manager.dart'; +import '../constant/enums/app_theme_enums.dart'; +import '../constant/enums/local_keys_enums.dart'; +import '../theme/app_theme_dark.dart'; +import '../theme/app_theme_light.dart'; + +class ThemeServices{ + + ThemeData _currentTheme(String theme) { + if (theme == AppThemes.LIGHT.name) { + return AppThemeLight.instance.theme; + } else if (theme == AppThemes.DARK.name) { + return AppThemeDark.instance.theme; + } else { + return AppThemeLight.instance.theme; + } + } + + Future getTheme() async { + await LocaleManager.prefrencesInit(); + String theme = LocaleManager.instance.getStringValue(PreferencesKeys.THEME); + return _currentTheme(theme); + } + + Future changeTheme(String theme) async { + await LocaleManager.prefrencesInit(); + LocaleManager.instance + .setStringValue(PreferencesKeys.THEME, theme); + return _currentTheme(theme); + } +} \ No newline at end of file diff --git a/lib/product/theme/provider/app_provider.dart b/lib/product/theme/provider/app_provider.dart deleted file mode 100644 index 39091ec..0000000 --- a/lib/product/theme/provider/app_provider.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:provider/provider.dart'; -import 'package:provider/single_child_widget.dart'; - -import '../theme_notifier.dart'; - -class ApplicationProvider { - static ApplicationProvider? _instance; - static ApplicationProvider get instance { - _instance ??= ApplicationProvider._init(); - return _instance!; - } - - ApplicationProvider._init(); - - List singleItems = []; - List dependItems = [ - ChangeNotifierProvider( - create: (context) => ThemeNotifier(), - ), - // Provider.value(value: NavigationService.instance) - ]; - List uiChangesItems = []; -} diff --git a/lib/product/theme/theme_notifier.dart b/lib/product/theme/theme_notifier.dart deleted file mode 100644 index 00351ef..0000000 --- a/lib/product/theme/theme_notifier.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'package:flutter/material.dart'; -import 'app_theme_dark.dart'; -import 'app_theme_light.dart'; - -import '../constant/enums/app_theme_enums.dart'; - -class ThemeNotifier extends ChangeNotifier { - // ThemeData _currentTheme = AppThemeLight.instance.theme; - // // ThemeData get currentTheme => LocaleManager.instance.getStringValue(PreferencesKeys.THEME) == - // // AppThemes.LIGHT.name - // // ? AppThemeLight.instance.theme - // // : AppThemeDark.instance.theme; - // ThemeData get currentTheme { - // log("ThemeKey: ${LocaleManager.instance.getStringValue(PreferencesKeys.THEME)}"); - // if (LocaleManager.instance.getStringValue(PreferencesKeys.THEME) == - // AppThemes.LIGHT.name) { - // log("light"); - // } else { - // log("dark"); - // } - // return LocaleManager.instance.getStringValue(PreferencesKeys.THEME) == - // AppThemes.LIGHT.name - // ? AppThemeLight.instance.theme - // : AppThemeDark.instance.theme; - // } - - // ThemeData _currentTheme = AppThemeLight.instance.theme; // Mặc định là light - - // ThemeNotifier() { - // loadThemeFromPreferences(); - // } - - // Future loadThemeFromPreferences() async { - // String themeKey = - // LocaleManager.instance.getStringValue(PreferencesKeys.THEME); - // log("ThemeNotifierKey:$themeKey "); - // if (themeKey == AppThemes.LIGHT.name) { - // _currentTheme = AppThemeLight.instance.theme; - // } else { - // _currentTheme = AppThemeDark.instance.theme; - // } - // notifyListeners(); // Thông báo cho các widget lắng nghe - // } - - // ThemeData get currentTheme => _currentTheme; - - // AppThemes _currenThemeEnum = AppThemes.LIGHT; - // AppThemes get currenThemeEnum => - // LocaleManager.instance.getStringValue(PreferencesKeys.THEME) == - // AppThemes.LIGHT.name - // ? AppThemes.LIGHT - // : AppThemes.DARK; - // // AppThemes get currenThemeEnum => _currenThemeEnum; - - // void changeValue(AppThemes theme) { - // if (theme == AppThemes.LIGHT) { - // _currentTheme = ThemeData.dark(); - // } else { - // _currentTheme = ThemeData.light(); - // } - // notifyListeners(); - // } - - // void changeTheme() { - // if (_currenThemeEnum == AppThemes.LIGHT) { - // _currentTheme = AppThemeDark.instance.theme; - // _currenThemeEnum = AppThemes.DARK; - // LocaleManager.instance - // .setString(PreferencesKeys.THEME, AppThemes.DARK.name); - // } else { - // _currentTheme = AppThemeLight.instance.theme; - // _currenThemeEnum = AppThemes.LIGHT; - // LocaleManager.instance - // .setString(PreferencesKeys.THEME, AppThemes.LIGHT.name); - // } - // notifyListeners(); - // } - - ThemeData _currentTheme = AppThemeLight.instance.theme; - ThemeData get currentTheme => _currentTheme; - - AppThemes _currenThemeEnum = AppThemes.LIGHT; - AppThemes get currenThemeEnum => _currenThemeEnum; - - Future loadThemeFromPreferences() async { - // String themeKey = - // LocaleManager.instance.getStringValue(PreferencesKeys.THEME); - // if (themeKey == AppThemes.LIGHT.name) { - // _currentTheme = AppThemeLight.instance.theme; - // } else { - // _currentTheme = AppThemeDark.instance.theme; - // } - // notifyListeners(); - } - - void changeValue(AppThemes theme) { - if (theme == AppThemes.LIGHT) { - _currentTheme = AppThemeLight.instance.theme; - } else { - _currentTheme = AppThemeDark.instance.theme; - } - notifyListeners(); - } - - void changeTheme() { - if (_currenThemeEnum == AppThemes.LIGHT) { - _currentTheme = AppThemeDark.instance.theme; - _currenThemeEnum = AppThemes.DARK; - } else { - _currentTheme = AppThemeLight.instance.theme; - _currenThemeEnum = AppThemes.LIGHT; - } - notifyListeners(); - } -}