From ff9cb4374e1a4eb6624f226d2c2d708d6a110a4a Mon Sep 17 00:00:00 2001 From: Ahmed Elsayed Date: Thu, 10 Aug 2023 08:01:31 +0300 Subject: [PATCH] Fix system navigation bar behavior for Android APIs 26-28 --- ios/Podfile.lock | 6 +++ lib/app.dart | 5 +- .../theme/presentation/utils/app_colors.dart | 4 ++ .../presentation/utils/app_colors_dark.dart | 7 ++- .../presentation/utils/app_colors_light.dart | 6 +-- .../theme/presentation/utils/app_theme.dart | 18 ++++--- .../extensions/device_info_extensions.dart | 9 ++++ .../presentation/helpers/theme_helper.dart | 14 ++++- .../providers/device_info_providers.dart | 17 ++++++ .../screens/full_screen_scaffold.dart | 4 ++ .../services/main_initializer.dart | 8 ++- .../full_screen_platform_scaffold.dart | 4 ++ .../map_screen/map_screen_compact.dart | 54 ++++++++----------- lib/main.dart | 5 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 24 +++++++++ pubspec.yaml | 1 + 17 files changed, 138 insertions(+), 50 deletions(-) create mode 100644 lib/core/presentation/extensions/device_info_extensions.dart create mode 100644 lib/core/presentation/providers/device_info_providers.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 2da0bbe5..3429c91e 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -642,6 +642,8 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift + - device_info_plus (0.0.1): + - Flutter - Firebase/Auth (10.10.0): - Firebase/CoreOnly - FirebaseAuth (~> 10.10.0) @@ -852,6 +854,7 @@ PODS: DEPENDENCIES: - cloud_firestore (from `.symlinks/plugins/cloud_firestore/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) @@ -902,6 +905,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cloud_firestore/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" firebase_auth: :path: ".symlinks/plugins/firebase_auth/ios" firebase_core: @@ -940,6 +945,7 @@ SPEC CHECKSUMS: BoringSSL-GRPC: 3175b25143e648463a56daeaaa499c6cb86dad33 cloud_firestore: 4dd02c206e4b7e93ff081cdd0e7fa304112bf906 connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea Firebase: facd334e557a979bd03a0b58d90fd56b52b8aba0 firebase_auth: 27829de4f51a06d305467eebc237157adc145571 firebase_core: 85b6664038311940ad60584eaabc73103c61f5de diff --git a/lib/app.dart b/lib/app.dart index a801c1c6..c02d85c7 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -4,6 +4,8 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'core/core_features/locale/presentation/providers/current_app_locale_provider.dart'; import 'core/core_features/theme/presentation/providers/current_app_theme_provider.dart'; import 'core/core_features/theme/presentation/utils/app_theme.dart'; +import 'core/presentation/extensions/device_info_extensions.dart'; +import 'core/presentation/providers/device_info_providers.dart'; import 'core/presentation/routing/app_router.dart'; import 'core/presentation/routing/navigation_service.dart'; import 'core/presentation/utils/riverpod_framework.dart'; @@ -19,6 +21,7 @@ class MyApp extends HookConsumerWidget { useOnPlatformBrightnessChange((previous, current) { ref.read(platformBrightnessProvider.notifier).update((_) => current); }); + final isOldAndroid = ref.watch(androidDeviceInfoProvider).requireValue.isOldAndroid; final themeMode = ref.watch(currentAppThemeModeProvider); final locale = ref.watch(currentAppLocaleProvider); @@ -36,7 +39,7 @@ class MyApp extends HookConsumerWidget { title: 'Deliverzler', debugShowCheckedModeBanner: false, color: Theme.of(context).colorScheme.primary, - theme: themeMode.getThemeData(locale.fontFamily), + theme: themeMode.getThemeData(locale.fontFamily, isOldAndroid: isOldAndroid), locale: Locale(locale.code), localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, diff --git a/lib/core/core_features/theme/presentation/utils/app_colors.dart b/lib/core/core_features/theme/presentation/utils/app_colors.dart index bb565816..20a52a7b 100644 --- a/lib/core/core_features/theme/presentation/utils/app_colors.dart +++ b/lib/core/core_features/theme/presentation/utils/app_colors.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'custom_colors.dart'; +part 'app_colors_light.dart'; +part 'app_colors_dark.dart'; + abstract class AppColors { ///Main // The background color for major parts of the app (toolbars, tab bars, etc) @@ -18,6 +21,7 @@ abstract class AppColors { ///Screen Color get statusBarColor; Color get systemNavBarColor; + Color get olderAndroidSystemNavBarColor; Color get appBarBGColor; Color get scaffoldBGColor; Color get navBarColor; diff --git a/lib/core/core_features/theme/presentation/utils/app_colors_dark.dart b/lib/core/core_features/theme/presentation/utils/app_colors_dark.dart index 04dc21bb..2ad144cd 100755 --- a/lib/core/core_features/theme/presentation/utils/app_colors_dark.dart +++ b/lib/core/core_features/theme/presentation/utils/app_colors_dark.dart @@ -1,7 +1,4 @@ -import 'package:flutter/material.dart'; - -import 'custom_colors.dart'; -import 'app_colors.dart'; +part of 'app_colors.dart'; class AppColorsDark implements AppColors { @override @@ -16,6 +13,8 @@ class AppColorsDark implements AppColors { @override Color get systemNavBarColor => Colors.transparent; @override + Color get olderAndroidSystemNavBarColor => scaffoldBGColor; + @override Color get appBarBGColor => scaffoldBGColor; @override Color get scaffoldBGColor => const Color(0xFF303030); diff --git a/lib/core/core_features/theme/presentation/utils/app_colors_light.dart b/lib/core/core_features/theme/presentation/utils/app_colors_light.dart index b6027809..162b8e4c 100755 --- a/lib/core/core_features/theme/presentation/utils/app_colors_light.dart +++ b/lib/core/core_features/theme/presentation/utils/app_colors_light.dart @@ -1,8 +1,6 @@ // ignore_for_file: overridden_fields -import 'package:flutter/material.dart'; -import 'custom_colors.dart'; -import 'app_colors.dart'; +part of 'app_colors.dart'; class AppColorsLight implements AppColors { @override @@ -17,6 +15,8 @@ class AppColorsLight implements AppColors { @override Color get systemNavBarColor => Colors.transparent; @override + Color get olderAndroidSystemNavBarColor => scaffoldBGColor; + @override Color get appBarBGColor => scaffoldBGColor; @override Color get scaffoldBGColor => const Color(0xFFFAFAFA); diff --git a/lib/core/core_features/theme/presentation/utils/app_theme.dart b/lib/core/core_features/theme/presentation/utils/app_theme.dart index f5961661..5df512a5 100644 --- a/lib/core/core_features/theme/presentation/utils/app_theme.dart +++ b/lib/core/core_features/theme/presentation/utils/app_theme.dart @@ -4,8 +4,6 @@ import 'package:flutter/services.dart'; import '../../../../presentation/styles/styles.dart'; import 'custom_colors.dart'; import 'app_colors.dart'; -import 'app_colors_dark.dart'; -import 'app_colors_light.dart'; enum AppThemeMode { light, @@ -13,8 +11,8 @@ enum AppThemeMode { } extension AppThemeModeX on AppThemeMode { - ThemeData getThemeData(String fontFamily) { - return AppTheme(themeMode: this).getThemeData(fontFamily); + ThemeData getThemeData(String fontFamily, {required bool isOldAndroid}) { + return AppTheme(themeMode: this, isOldAndroid: isOldAndroid).getThemeData(fontFamily); } ThemeData get _baseTheme { @@ -58,10 +56,14 @@ extension AppThemeModeX on AppThemeMode { } class AppTheme { - AppTheme({required AppThemeMode themeMode}) : _themeMode = themeMode; + AppTheme({required AppThemeMode themeMode, required bool isOldAndroid}) + : _isOldAndroid = isOldAndroid, + _themeMode = themeMode; final AppThemeMode _themeMode; + final bool _isOldAndroid; + late final ThemeData _baseTheme = _themeMode._baseTheme; late final AppColors _appColors = _themeMode._colorsPalette; @@ -77,7 +79,11 @@ class AppTheme { systemOverlayStyle: _themeMode._baseOverlayStyle.copyWith( //For Android statusBarColor: _appColors.statusBarColor, - systemNavigationBarColor: _appColors.systemNavBarColor, + // Not using transparent color for old android versions to avoid hidden navigation bar icons: + // https://github.com/flutter/flutter/issues/105716 + systemNavigationBarColor: + _isOldAndroid ? _appColors.olderAndroidSystemNavBarColor : _appColors.systemNavBarColor, + systemNavigationBarDividerColor: _appColors.systemNavBarColor, ), backgroundColor: _appColors.appBarBGColor, elevation: Sizes.appBarElevation, diff --git a/lib/core/presentation/extensions/device_info_extensions.dart b/lib/core/presentation/extensions/device_info_extensions.dart new file mode 100644 index 00000000..ee5beabd --- /dev/null +++ b/lib/core/presentation/extensions/device_info_extensions.dart @@ -0,0 +1,9 @@ +import 'package:device_info_plus/device_info_plus.dart'; + +import '../utils/fp_framework.dart'; + +extension AndroidDeviceInfoX on Option { + bool get isOldAndroid { + return match(() => false, (info) => info.version.sdkInt < 29); + } +} diff --git a/lib/core/presentation/helpers/theme_helper.dart b/lib/core/presentation/helpers/theme_helper.dart index 66370900..bf009f79 100644 --- a/lib/core/presentation/helpers/theme_helper.dart +++ b/lib/core/presentation/helpers/theme_helper.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; +import '../../core_features/theme/presentation/utils/app_colors.dart'; import '../../core_features/theme/presentation/utils/app_theme.dart'; bool isDarkMode([Brightness? platformBrightness]) { @@ -19,18 +20,27 @@ AppThemeMode getSystemTheme([Brightness? platformBrightness]) { SystemUiOverlayStyle getFullScreenOverlayStyle( BuildContext context, { required bool darkOverlays, + required bool isOldAndroid, }) { + final systemNavBarColor = switch (isOldAndroid) { + true when darkOverlays => AppColorsLight().olderAndroidSystemNavBarColor, + true when !darkOverlays => AppColorsDark().olderAndroidSystemNavBarColor, + _ => Colors.transparent, + }; + return darkOverlays ? SystemUiOverlayStyle.dark.copyWith( //For Android statusBarColor: Colors.transparent, - systemNavigationBarColor: Colors.transparent, + systemNavigationBarColor: systemNavBarColor, + systemNavigationBarDividerColor: systemNavBarColor, systemNavigationBarIconBrightness: Brightness.dark, ) : SystemUiOverlayStyle.light.copyWith( //For Android statusBarColor: Colors.transparent, - systemNavigationBarColor: Colors.transparent, + systemNavigationBarColor: systemNavBarColor, + systemNavigationBarDividerColor: systemNavBarColor, systemNavigationBarIconBrightness: Brightness.light, ); } diff --git a/lib/core/presentation/providers/device_info_providers.dart b/lib/core/presentation/providers/device_info_providers.dart new file mode 100644 index 00000000..fb3d62f4 --- /dev/null +++ b/lib/core/presentation/providers/device_info_providers.dart @@ -0,0 +1,17 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; + +import '../utils/fp_framework.dart'; +import '../utils/riverpod_framework.dart'; + +part 'device_info_providers.g.dart'; + +@Riverpod(keepAlive: true) +FutureOr> androidDeviceInfo(AndroidDeviceInfoRef ref) async { + ref.onDispose(() { + print('dispooose'); + }); + if (Platform.isAndroid) return await DeviceInfoPlugin().androidInfo.then(Some.new); + return const None(); +} diff --git a/lib/core/presentation/screens/full_screen_scaffold.dart b/lib/core/presentation/screens/full_screen_scaffold.dart index 18c56ecc..5e6fc079 100644 --- a/lib/core/presentation/screens/full_screen_scaffold.dart +++ b/lib/core/presentation/screens/full_screen_scaffold.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import '../../core_features/theme/presentation/providers/current_app_theme_provider.dart'; import '../../core_features/theme/presentation/utils/app_theme.dart'; +import '../extensions/device_info_extensions.dart'; import '../helpers/theme_helper.dart'; +import '../providers/device_info_providers.dart'; import '../utils/riverpod_framework.dart'; import '../widgets/status_bar_spacer.dart'; @@ -23,6 +25,7 @@ class FullScreenScaffold extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentTheme = ref.watch(currentAppThemeModeProvider); + final isOldAndroid = ref.watch(androidDeviceInfoProvider).requireValue.isOldAndroid; return Scaffold( appBar: hasStatusBarSpace ? StatusBarSpacer(statusBarColor: statusBarColor) : null, @@ -30,6 +33,7 @@ class FullScreenScaffold extends ConsumerWidget { value: getFullScreenOverlayStyle( context, darkOverlays: darkOverlays ?? currentTheme == AppThemeMode.light, + isOldAndroid: isOldAndroid, ), child: body, ), diff --git a/lib/core/presentation/services/main_initializer.dart b/lib/core/presentation/services/main_initializer.dart index 2c17dff0..5e4fb100 100755 --- a/lib/core/presentation/services/main_initializer.dart +++ b/lib/core/presentation/services/main_initializer.dart @@ -1,11 +1,17 @@ part of '../../../main.dart'; -Future _mainInitializer() async { +Future _mainInitializer(ProviderContainer container) async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); _setupLogger(); await _initFirebase(); + + // Caching the android device info to be used synchronously at AppTheme to setup the navigation bar + // behavior for older Android versions without flickering (of the navigation bar) when app starts. + await container.read(androidDeviceInfoProvider.future).silenceError(); + // This Prevent closing native splash screen until we finish warming-up custom splash images. // App layout will be built but not displayed. + // Also, theme/locale will be loaded (async) during this time (A default theme/locale is set initially). widgetsBinding.deferFirstFrame(); widgetsBinding.addPostFrameCallback((_) async { // Run any function you want to wait for before showing app layout. diff --git a/lib/core/presentation/widgets/legacy/platform_widgets/full_screen_platform_scaffold.dart b/lib/core/presentation/widgets/legacy/platform_widgets/full_screen_platform_scaffold.dart index 356a7ce4..1d895a1b 100644 --- a/lib/core/presentation/widgets/legacy/platform_widgets/full_screen_platform_scaffold.dart +++ b/lib/core/presentation/widgets/legacy/platform_widgets/full_screen_platform_scaffold.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; import '../../../../core_features/theme/presentation/providers/current_app_theme_provider.dart'; import '../../../../core_features/theme/presentation/utils/app_theme.dart'; +import '../../../extensions/device_info_extensions.dart'; import '../../../helpers/theme_helper.dart'; +import '../../../providers/device_info_providers.dart'; import '../../../utils/riverpod_framework.dart'; import 'platform_scaffold.dart'; @@ -19,6 +21,7 @@ class FullScreenPlatformScaffold extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final currentTheme = ref.watch(currentAppThemeModeProvider); + final isOldAndroid = ref.watch(androidDeviceInfoProvider).requireValue.isOldAndroid; return PlatformScaffold( hasStatusBarSpace: false, @@ -26,6 +29,7 @@ class FullScreenPlatformScaffold extends ConsumerWidget { value: getFullScreenOverlayStyle( context, darkOverlays: darkOverlays ?? currentTheme == AppThemeMode.light, + isOldAndroid: isOldAndroid, ), child: body, ), diff --git a/lib/features/map/presentation/screens/map_screen/map_screen_compact.dart b/lib/features/map/presentation/screens/map_screen/map_screen_compact.dart index 226d210d..77e040a6 100755 --- a/lib/features/map/presentation/screens/map_screen/map_screen_compact.dart +++ b/lib/features/map/presentation/screens/map_screen/map_screen_compact.dart @@ -1,9 +1,6 @@ import 'package:flutter/material.dart'; -import '../../../../../core/core_features/theme/presentation/providers/current_app_theme_provider.dart'; -import '../../../../../core/core_features/theme/presentation/utils/app_theme.dart'; import '../../../../../core/presentation/helpers/localization_helper.dart'; -import '../../../../../core/presentation/helpers/theme_helper.dart'; import '../../../../../core/presentation/routing/app_router.dart'; import '../../../../../core/presentation/screens/nested_screen_scaffold.dart'; import '../../../../../core/presentation/services/local_notfication_service/show_local_notification_provider.dart'; @@ -29,7 +26,6 @@ class MapScreenCompact extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final currentTheme = ref.watch(currentAppThemeModeProvider); final locationAsync = ref.watch(locationStreamProvider); useEffect( @@ -67,34 +63,28 @@ class MapScreenCompact extends HookConsumerWidget { }, ); - return AnnotatedRegion( - value: getFullScreenOverlayStyle( - context, - darkOverlays: currentTheme == AppThemeMode.light, - ), - child: NestedScreenScaffold( - body: locationAsync.when( - skipLoadingOnReload: true, - skipLoadingOnRefresh: !locationAsync.hasError, - loading: () => TitledLoadingIndicator(message: tr(context).determine_location), - error: (error, st) => RetryAgainComponent( - description: (error as LocationError).getErrorText(context), - onPressed: () { - ref.invalidate(locationStreamProvider); - }, - ), - data: (_) => const Stack( - alignment: Alignment.topCenter, - fit: StackFit.expand, - children: [ - GoogleMapComponent(), - MapDirectionsInfoComponent(), - MapPhoneCallComponent(), - MapConfirmButtonComponent(), - MapFloatingSearchBar(), - MapFloatingActionButton(), - ], - ), + return NestedScreenScaffold( + body: locationAsync.when( + skipLoadingOnReload: true, + skipLoadingOnRefresh: !locationAsync.hasError, + loading: () => TitledLoadingIndicator(message: tr(context).determine_location), + error: (error, st) => RetryAgainComponent( + description: (error as LocationError).getErrorText(context), + onPressed: () { + ref.invalidate(locationStreamProvider); + }, + ), + data: (_) => const Stack( + alignment: Alignment.topCenter, + fit: StackFit.expand, + children: [ + GoogleMapComponent(), + MapDirectionsInfoComponent(), + MapPhoneCallComponent(), + MapConfirmButtonComponent(), + MapFloatingSearchBar(), + MapFloatingActionButton(), + ], ), ), ); diff --git a/lib/main.dart b/lib/main.dart index df94fdaa..366e4ca5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:logging/logging.dart'; import 'app.dart'; import 'core/presentation/extensions/future_extensions.dart'; +import 'core/presentation/providers/device_info_providers.dart'; import 'core/presentation/providers/provider_observers.dart'; import 'core/presentation/utils/riverpod_framework.dart'; import 'firebase_options.dart'; @@ -17,9 +18,11 @@ import 'gen/my_assets.dart'; part 'core/presentation/services/main_initializer.dart'; void main() async { - await _mainInitializer(); + final container = ProviderContainer(); + await _mainInitializer(container); runApp( ProviderScope( + parent: container, observers: [ProviderLogger(), ProviderCrashlytics()], child: const MyApp(), ), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index baf3a9fd..4f58fe86 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,6 +7,7 @@ import Foundation import cloud_firestore import connectivity_plus +import device_info_plus import firebase_auth import firebase_core import firebase_messaging @@ -22,6 +23,7 @@ import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin")) diff --git a/pubspec.lock b/pubspec.lock index df4f6205..03f66db4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -337,6 +337,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.8" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + sha256: "86add5ef97215562d2e090535b0a16f197902b10c369c558a100e74ea06e8659" + url: "https://pub.dev" + source: hosted + version: "9.0.3" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + url: "https://pub.dev" + source: hosted + version: "7.0.0" dio: dependency: "direct main" description: @@ -1627,6 +1643,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.4" + win32_registry: + dependency: transitive + description: + name: win32_registry + sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 64ee886c..fd6160b2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: # Utils mocktail: ^0.3.0 + device_info_plus: ^9.0.3 fast_immutable_collections: ^9.1.5 flutter_fadein: ^2.0.0 uuid: ^3.0.7