refactor(theme&ui): improve theme switching and navigation styling

This commit is contained in:
anhtunz
2024-12-26 14:52:33 +07:00
parent a69429b05f
commit 70e3ed8978
6 changed files with 255 additions and 366 deletions

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:sfm_app/product/base/bloc/base_bloc.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'; import '../bell/bell_model.dart';
@@ -15,6 +16,10 @@ class MainBloc extends BlocBase {
StreamSink<Locale?> get sinkLanguage => language.sink; StreamSink<Locale?> get sinkLanguage => language.sink;
Stream<Locale?> get streamLanguage => language.stream; Stream<Locale?> get streamLanguage => language.stream;
final theme = StreamController<ThemeData?>.broadcast();
StreamSink<ThemeData?> get sinkTheme => theme.sink;
Stream<ThemeData?> get streamTheme => theme.stream;
final themeMode = StreamController<bool>.broadcast(); final themeMode = StreamController<bool>.broadcast();
StreamSink<bool> get sinkThemeMode => themeMode.sink; StreamSink<bool> get sinkThemeMode => themeMode.sink;
Stream<bool> get streamThemeMode => themeMode.stream; Stream<bool> get streamThemeMode => themeMode.stream;

View File

@@ -6,15 +6,12 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent-tab-view.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:badges/badges.dart' as badges;
import 'package:sfm_app/feature/home/home_bloc.dart'; import '../home/home_bloc.dart';
import 'package:sfm_app/product/constant/app/app_constants.dart'; import '../../product/constant/app/app_constants.dart';
import 'package:sfm_app/product/constant/enums/app_route_enums.dart'; import '../../product/constant/enums/app_route_enums.dart';
import 'package:sfm_app/product/constant/enums/role_enums.dart'; import '../../product/permission/location_permission.dart';
import 'package:sfm_app/product/permission/location_permission.dart'; import '../../product/services/theme_services.dart';
import 'package:sfm_app/product/shared/shared_language_switch.dart';
import '../../product/shared/shared_light_dark_switch.dart';
import '../devices/devices_manager_bloc.dart'; import '../devices/devices_manager_bloc.dart';
import '../devices/devices_manager_screen.dart'; import '../devices/devices_manager_screen.dart';
import '../home/home_screen.dart'; import '../home/home_screen.dart';
@@ -27,13 +24,11 @@ import '../map/map_bloc.dart';
import '../map/map_screen.dart'; import '../map/map_screen.dart';
import '../../product/base/bloc/base_bloc.dart'; import '../../product/base/bloc/base_bloc.dart';
import '../../product/constant/enums/app_theme_enums.dart'; import '../../product/constant/enums/app_theme_enums.dart';
import '../../product/extention/context_extention.dart';
import '../../product/services/api_services.dart'; import '../../product/services/api_services.dart';
import '../../main.dart'; import '../../main.dart';
import '../../product/constant/icon/icon_constants.dart'; import '../../product/constant/icon/icon_constants.dart';
import '../../product/constant/lang/language_constants.dart'; import '../../product/constant/lang/language_constants.dart';
import '../../product/services/language_services.dart'; import '../../product/services/language_services.dart';
import '../../product/theme/theme_notifier.dart';
import '../bell/bell_model.dart'; import '../bell/bell_model.dart';
class MainScreen extends StatefulWidget { class MainScreen extends StatefulWidget {
@@ -69,16 +64,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
} }
mainBloc.sinkIsVNIcon.add(isVN); mainBloc.sinkIsVNIcon.add(isVN);
mainBloc.sinkThemeMode.add(isLight); mainBloc.sinkThemeMode.add(isLight);
log("role: $role");
LocationPermissionRequest.instance.checkLocationPermission(context); LocationPermissionRequest.instance.checkLocationPermission(context);
} }
// For test
late bool dayNightToggle2;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
dayNightToggle2 = false;
mainBloc = BlocProvider.of(context); mainBloc = BlocProvider.of(context);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
initialCheck(); initialCheck();
@@ -178,7 +169,10 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
ThemeNotifier themeNotifier = context.watch<ThemeNotifier>(); return StreamBuilder<bool>(
stream: mainBloc.streamThemeMode,
initialData: isLight,
builder: (context, themeModeSnapshot) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
@@ -211,23 +205,19 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
// SizedBox( // SizedBox(
// width: context.lowValue, // width: context.lowValue,
// ), // ),
StreamBuilder<bool>( IconButton(
stream: mainBloc.streamThemeMode, onPressed: () async {
initialData: isLight, ThemeData newTheme = await ThemeServices().changeTheme(
builder: (context, themeModeSnapshot) { isLight ? AppThemes.DARK.name : AppThemes.LIGHT.name);
return IconButton( MyApp.setTheme(context, newTheme);
onPressed: () {
themeNotifier.changeTheme();
isLight = !isLight; isLight = !isLight;
mainBloc.sinkThemeMode.add(isLight); mainBloc.sinkThemeMode.add(isLight);
}, },
icon: Icon( icon: Icon(
themeModeSnapshot.data ?? isLight themeModeSnapshot.data ?? isLight
? Icons.light_mode_outlined ? Icons.dark_mode_outlined
: Icons.dark_mode_outlined, : Icons.light_mode_outlined,
), ),
);
},
), ),
StreamBuilder<bool>( StreamBuilder<bool>(
stream: mainBloc.streamIsVNIcon, stream: mainBloc.streamIsVNIcon,
@@ -277,7 +267,8 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
), ),
badgeAnimation: const badges.BadgeAnimation.slide( badgeAnimation: const badges.BadgeAnimation.slide(
animationDuration: Duration(milliseconds: 200), animationDuration: Duration(milliseconds: 200),
colorChangeAnimationDuration: Duration(seconds: 1), colorChangeAnimationDuration:
Duration(seconds: 1),
loopAnimation: false, loopAnimation: false,
curve: Curves.decelerate, curve: Curves.decelerate,
colorChangeAnimationCurve: Curves.easeInCirc, colorChangeAnimationCurve: Curves.easeInCirc,
@@ -340,28 +331,6 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
) )
], ],
), ),
// 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( body: PersistentTabView(
context, context,
controller: controller, controller: controller,
@@ -372,9 +341,12 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
stateManagement: true, stateManagement: true,
hideNavigationBarWhenKeyboardShows: true, hideNavigationBarWhenKeyboardShows: true,
// backgroundColor: Colors.transparent, backgroundColor:
themeModeSnapshot.data! ? Colors.white : Colors.black,
decoration: NavBarDecoration( decoration: NavBarDecoration(
borderRadius: BorderRadius.circular(30.0), borderRadius: BorderRadius.circular(30.0),
colorBehindNavBar:
themeModeSnapshot.data! ? Colors.white : Colors.black,
), ),
popAllScreensOnTapOfSelectedTab: true, popAllScreensOnTapOfSelectedTab: true,
itemAnimationProperties: const ItemAnimationProperties( itemAnimationProperties: const ItemAnimationProperties(
@@ -389,6 +361,8 @@ class _MainScreenState extends State<MainScreen> with WidgetsBindingObserver {
navBarStyle: NavBarStyle.style4, navBarStyle: NavBarStyle.style4,
), ),
); );
},
);
} }
Future<void> getBellNotification() async { Future<void> getBellNotification() async {

View File

@@ -1,25 +1,20 @@
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.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 'product/services/language_services.dart';
import 'feature/main/main_bloc.dart'; import 'feature/main/main_bloc.dart';
import 'product/base/bloc/base_bloc.dart'; import 'product/base/bloc/base_bloc.dart';
import 'product/constant/navigation/navigation_router.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'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); await Firebase.initializeApp();
runApp( runApp(
MultiProvider( BlocProvider(
providers: [...ApplicationProvider.instance.dependItems],
child: BlocProvider(
child: const MyApp(), child: const MyApp(),
blocBuilder: () => MainBloc(), blocBuilder: () => MainBloc(),
), ),
),
); );
} }
@@ -32,17 +27,30 @@ class MyApp extends StatefulWidget {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>(); _MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.setLocale(newLocale); state?.setLocale(newLocale);
} }
static void setTheme(BuildContext context, ThemeData newTheme) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state?.setTheme(newTheme);
}
} }
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
Locale? _locale; Locale? _locale;
ThemeData? _themeData;
late MainBloc mainBloc; late MainBloc mainBloc;
LanguageServices languageServices = LanguageServices(); LanguageServices languageServices = LanguageServices();
ThemeServices themeServices = ThemeServices();
setLocale(Locale locale) { setLocale(Locale locale) {
_locale = locale; _locale = locale;
mainBloc.sinkLanguage.add(_locale); mainBloc.sinkLanguage.add(_locale);
} }
setTheme(ThemeData theme) {
_themeData = theme;
mainBloc.sinkTheme.add(_themeData);
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -52,6 +60,7 @@ class _MyAppState extends State<MyApp> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
languageServices.getLocale().then((locale) => {setLocale(locale)}); languageServices.getLocale().then((locale) => {setLocale(locale)});
themeServices.getTheme().then((theme) => {setTheme(theme)});
super.didChangeDependencies(); super.didChangeDependencies();
} }
@@ -62,13 +71,19 @@ class _MyAppState extends State<MyApp> {
stream: mainBloc.streamLanguage, stream: mainBloc.streamLanguage,
initialData: _locale, initialData: _locale,
builder: (context, languageSnapshot) { builder: (context, languageSnapshot) {
return StreamBuilder<ThemeData?>(
stream: mainBloc.streamTheme,
initialData: _themeData,
builder: (context, themeSnapshot) {
return MaterialApp.router( return MaterialApp.router(
theme: context.watch<ThemeNotifier>().currentTheme, theme: themeSnapshot.data,
routerConfig: router, routerConfig: router,
localizationsDelegates: AppLocalizations.localizationsDelegates, localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
locale: languageSnapshot.data, locale: languageSnapshot.data,
); );
}
);
}, },
); );
} }

View File

@@ -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<ThemeData> getTheme() async {
await LocaleManager.prefrencesInit();
String theme = LocaleManager.instance.getStringValue(PreferencesKeys.THEME);
return _currentTheme(theme);
}
Future<ThemeData> changeTheme(String theme) async {
await LocaleManager.prefrencesInit();
LocaleManager.instance
.setStringValue(PreferencesKeys.THEME, theme);
return _currentTheme(theme);
}
}

View File

@@ -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<SingleChildWidget> singleItems = [];
List<SingleChildWidget> dependItems = [
ChangeNotifierProvider(
create: (context) => ThemeNotifier(),
),
// Provider.value(value: NavigationService.instance)
];
List<SingleChildWidget> uiChangesItems = [];
}

View File

@@ -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<void> 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<void> 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();
}
}