diff --git a/lib/application.dart b/lib/application.dart index 3880a08..1249ed9 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -1,13 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:lightmeter/data/caffeine_service.dart'; +import 'package:lightmeter/data/haptics_service.dart'; +import 'package:lightmeter/data/light_sensor_service.dart'; import 'package:lightmeter/data/models/supported_locale.dart'; +import 'package:lightmeter/data/permissions_service.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/services_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/metering/flow_metering.dart'; import 'package:lightmeter/screens/settings/flow_settings.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:platform/platform.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class Application extends StatelessWidget { final Environment env; @@ -16,56 +25,71 @@ class Application extends StatelessWidget { @override Widget build(BuildContext context) { - return LightmeterProviders( - env: env, - builder: (context, ready) => ready - ? _AnnotatedRegionWrapper( - child: MaterialApp( - theme: context.listen(), - locale: Locale(context.listen().intlName), - localizationsDelegates: const [ - S.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: S.delegate.supportedLocales, - builder: (context, child) => MediaQuery( - data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), - child: child!, + return FutureBuilder( + future: Future.wait([ + SharedPreferences.getInstance(), + const LightSensorService(LocalPlatform()).hasSensor(), + ]), + builder: (_, snapshot) { + if (snapshot.data != null) { + return ServicesProvider( + caffeineService: const CaffeineService(), + environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool), + hapticsService: const HapticsService(), + lightSensorService: const LightSensorService(LocalPlatform()), + permissionsService: const PermissionsService(), + userPreferencesService: UserPreferencesService(snapshot.data![0] as SharedPreferences), + volumeEventsService: const VolumeEventsService(LocalPlatform()), + child: UserPreferencesProvider( + child: EquipmentProfileProvider( + child: Builder( + builder: (context) { + final theme = UserPreferencesProvider.themeOf(context); + final systemIconsBrightness = + ThemeData.estimateBrightnessForColor(theme.colorScheme.onSurface); + return AnnotatedRegion( + value: SystemUiOverlayStyle( + statusBarColor: Colors.transparent, + statusBarBrightness: systemIconsBrightness == Brightness.light + ? Brightness.dark + : Brightness.light, + statusBarIconBrightness: systemIconsBrightness, + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: systemIconsBrightness, + ), + child: MaterialApp( + theme: theme, + locale: Locale(UserPreferencesProvider.localeOf(context).intlName), + localizationsDelegates: const [ + S.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: S.delegate.supportedLocales, + builder: (context, child) => MediaQuery( + data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0), + child: child!, + ), + initialRoute: "metering", + routes: { + "metering": (context) => const MeteringFlow(), + "settings": (context) => const SettingsFlow(), + }, + ), + ); + }, ), - initialRoute: "metering", - routes: { - "metering": (context) => const MeteringFlow(), - "settings": (context) => const SettingsFlow(), - }, ), - ) - : const SizedBox(), - ); - } -} - -class _AnnotatedRegionWrapper extends StatelessWidget { - final Widget child; - - const _AnnotatedRegionWrapper({required this.child}); - - @override - Widget build(BuildContext context) { - final systemIconsBrightness = ThemeData.estimateBrightnessForColor( - context.listen().colorScheme.onSurface, - ); - return AnnotatedRegion( - value: SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - statusBarBrightness: - systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light, - statusBarIconBrightness: systemIconsBrightness, - systemNavigationBarColor: Colors.transparent, - systemNavigationBarIconBrightness: systemIconsBrightness, - ), - child: child, + ), + ); + } else if (snapshot.error != null) { + return Center(child: Text(snapshot.error!.toString())); + } + + // TODO(@vodemn): maybe user splashscreen instead + return const SizedBox(); + }, ); } } diff --git a/lib/providers.dart b/lib/providers.dart deleted file mode 100644 index d7907ab..0000000 --- a/lib/providers.dart +++ /dev/null @@ -1,77 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/data/caffeine_service.dart'; -import 'package:lightmeter/data/haptics_service.dart'; -import 'package:lightmeter/data/light_sensor_service.dart'; -import 'package:lightmeter/data/permissions_service.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/data/volume_events_service.dart'; -import 'package:lightmeter/environment.dart'; -import 'package:lightmeter/providers/equipment_profile_provider.dart'; -import 'package:lightmeter/providers/ev_source_type_provider.dart'; -import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; -import 'package:lightmeter/providers/stop_type_provider.dart'; -import 'package:lightmeter/providers/supported_locale_provider.dart'; -import 'package:lightmeter/providers/theme_provider.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; -import 'package:platform/platform.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -class LightmeterProviders extends StatelessWidget { - final Environment env; - final Widget Function(BuildContext context, bool ready) builder; - - const LightmeterProviders({required this.env, required this.builder, super.key}); - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: Future.wait([ - SharedPreferences.getInstance(), - const LightSensorService(LocalPlatform()).hasSensor(), - ]), - builder: (_, snapshot) { - if (snapshot.data != null) { - return InheritedWidgetBase( - data: env.copyWith(hasLightSensor: snapshot.data![1] as bool), - child: InheritedWidgetBase( - data: UserPreferencesService(snapshot.data![0] as SharedPreferences), - child: InheritedWidgetBase( - data: const LightSensorService(LocalPlatform()), - child: InheritedWidgetBase( - data: const CaffeineService(), - child: InheritedWidgetBase( - data: const HapticsService(), - child: InheritedWidgetBase( - data: const VolumeEventsService(LocalPlatform()), - child: InheritedWidgetBase( - data: const PermissionsService(), - child: MeteringScreenLayoutProvider( - child: StopTypeProvider( - child: EquipmentProfileProvider( - child: EvSourceTypeProvider( - child: SupportedLocaleProvider( - child: ThemeProvider( - child: Builder( - builder: (context) => builder(context, true), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ), - ); - } else if (snapshot.error != null) { - return Center(child: Text(snapshot.error!.toString())); - } - return builder(context, false); - }, - ); - } -} diff --git a/lib/providers/equipment_profile_provider.dart b/lib/providers/equipment_profile_provider.dart index 85c9377..c0294fa 100644 --- a/lib/providers/equipment_profile_provider.dart +++ b/lib/providers/equipment_profile_provider.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:uuid/uuid.dart'; -typedef EquipmentProfiles = List; - +// TODO(@vodemn): This will be removed in #89 class EquipmentProfileProvider extends StatefulWidget { final Widget child; @@ -35,7 +33,8 @@ class EquipmentProfileProviderState extends State { EquipmentProfile get _selectedProfile => _customProfiles.firstWhere( (e) => e.id == _selectedId, orElse: () { - context.get().selectedEquipmentProfileId = _defaultProfile.id; + ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId = + _defaultProfile.id; return _defaultProfile; }, ); @@ -43,18 +42,16 @@ class EquipmentProfileProviderState extends State { @override void initState() { super.initState(); - _selectedId = context.get().selectedEquipmentProfileId; - _customProfiles = context.get().equipmentProfiles; + _selectedId = ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId; + _customProfiles = ServicesProvider.of(context).userPreferencesService.equipmentProfiles; } @override Widget build(BuildContext context) { - return InheritedWidgetBase>( - data: [_defaultProfile] + _customProfiles, - child: InheritedWidgetBase( - data: _selectedProfile, - child: widget.child, - ), + return EquipmentProfiles( + profiles: [_defaultProfile] + _customProfiles, + selected: _selectedProfile, + child: widget.child, ); } @@ -62,7 +59,8 @@ class EquipmentProfileProviderState extends State { setState(() { _selectedId = data.id; }); - context.get().selectedEquipmentProfileId = _selectedProfile.id; + ServicesProvider.of(context).userPreferencesService.selectedEquipmentProfileId = + _selectedProfile.id; } /// Creates a default equipment profile @@ -94,7 +92,48 @@ class EquipmentProfileProviderState extends State { } void _refreshSavedProfiles() { - context.get().equipmentProfiles = _customProfiles; + ServicesProvider.of(context).userPreferencesService.equipmentProfiles = _customProfiles; setState(() {}); } } + +// Copied from #89 +enum EquipmentProfilesAspect { list, selected } + +class EquipmentProfiles extends InheritedModel { + const EquipmentProfiles({ + super.key, + required this.profiles, + required this.selected, + required super.child, + }); + + final List profiles; + final EquipmentProfile selected; + + static List of(BuildContext context) { + return InheritedModel.inheritFrom( + context, + aspect: EquipmentProfilesAspect.list, + )! + .profiles; + } + + static EquipmentProfile selectedOf(BuildContext context) { + return InheritedModel.inheritFrom( + context, + aspect: EquipmentProfilesAspect.selected, + )! + .selected; + } + + @override + bool updateShouldNotify(EquipmentProfiles oldWidget) => false; + + @override + bool updateShouldNotifyDependent( + EquipmentProfiles oldWidget, + Set dependencies, + ) => + false; +} diff --git a/lib/providers/ev_source_type_provider.dart b/lib/providers/ev_source_type_provider.dart deleted file mode 100644 index aed873b..0000000 --- a/lib/providers/ev_source_type_provider.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/data/models/ev_source_type.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/environment.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; - -class EvSourceTypeProvider extends StatefulWidget { - final Widget child; - - const EvSourceTypeProvider({required this.child, super.key}); - - static EvSourceTypeProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - @override - State createState() => EvSourceTypeProviderState(); -} - -class EvSourceTypeProviderState extends State { - late final ValueNotifier valueListenable; - - @override - void initState() { - super.initState(); - final evSourceType = context.get().evSourceType; - valueListenable = ValueNotifier( - evSourceType == EvSourceType.sensor && !context.get().hasLightSensor - ? EvSourceType.camera - : evSourceType, - ); - } - - @override - void dispose() { - valueListenable.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: valueListenable, - builder: (_, value, child) => InheritedWidgetBase( - data: value, - child: child!, - ), - child: widget.child, - ); - } - - void toggleType() { - switch (valueListenable.value) { - case EvSourceType.camera: - if (context.get().hasLightSensor) { - valueListenable.value = EvSourceType.sensor; - } - case EvSourceType.sensor: - valueListenable.value = EvSourceType.camera; - } - context.get().evSourceType = valueListenable.value; - } -} diff --git a/lib/providers/metering_screen_layout_provider.dart b/lib/providers/metering_screen_layout_provider.dart deleted file mode 100644 index 405c1a7..0000000 --- a/lib/providers/metering_screen_layout_provider.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; - -class MeteringScreenLayoutProvider extends StatefulWidget { - final Widget child; - - const MeteringScreenLayoutProvider({required this.child, super.key}); - - static MeteringScreenLayoutProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - @override - State createState() => MeteringScreenLayoutProviderState(); -} - -class MeteringScreenLayoutProviderState extends State { - late final MeteringScreenLayoutConfig _config = - context.get().meteringScreenLayout; - - @override - Widget build(BuildContext context) { - return InheritedModelBase( - data: MeteringScreenLayoutConfig.from(_config), - child: widget.child, - ); - } - - void updateFeatures(MeteringScreenLayoutConfig config) { - setState(() { - config.forEach((key, value) { - _config.update( - key, - (_) => value, - ifAbsent: () => value, - ); - }); - }); - context.get().meteringScreenLayout = _config; - } -} - -typedef _MeteringScreenLayoutModel = InheritedModelBase; - -extension MeteringScreenLayout on InheritedModelBase { - static MeteringScreenLayoutConfig of(BuildContext context, {bool listen = true}) { - if (listen) { - return context.dependOnInheritedWidgetOfExactType<_MeteringScreenLayoutModel>()!.data; - } else { - return context.findAncestorWidgetOfExactType<_MeteringScreenLayoutModel>()!.data; - } - } - - static bool featureOf(BuildContext context, MeteringScreenLayoutFeature aspect) { - return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: aspect)! - .data[aspect]!; - } -} diff --git a/lib/providers/services_provider.dart b/lib/providers/services_provider.dart new file mode 100644 index 0000000..c2c548f --- /dev/null +++ b/lib/providers/services_provider.dart @@ -0,0 +1,36 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/caffeine_service.dart'; +import 'package:lightmeter/data/haptics_service.dart'; +import 'package:lightmeter/data/light_sensor_service.dart'; +import 'package:lightmeter/data/permissions_service.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/data/volume_events_service.dart'; +import 'package:lightmeter/environment.dart'; + +class ServicesProvider extends InheritedWidget { + final CaffeineService caffeineService; + final Environment environment; + final HapticsService hapticsService; + final LightSensorService lightSensorService; + final PermissionsService permissionsService; + final UserPreferencesService userPreferencesService; + final VolumeEventsService volumeEventsService; + + const ServicesProvider({ + required this.caffeineService, + required this.environment, + required this.hapticsService, + required this.lightSensorService, + required this.permissionsService, + required this.userPreferencesService, + required this.volumeEventsService, + required super.child, + }); + + static ServicesProvider of(BuildContext context) { + return context.findAncestorWidgetOfExactType()!; + } + + @override + bool updateShouldNotify(ServicesProvider oldWidget) => false; +} diff --git a/lib/providers/stop_type_provider.dart b/lib/providers/stop_type_provider.dart deleted file mode 100644 index 3b20dd5..0000000 --- a/lib/providers/stop_type_provider.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; -import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; - -class StopTypeProvider extends StatefulWidget { - final Widget child; - - const StopTypeProvider({required this.child, super.key}); - - static StopTypeProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - @override - State createState() => StopTypeProviderState(); -} - -class StopTypeProviderState extends State { - late StopType _stopType; - - @override - void initState() { - super.initState(); - _stopType = context.get().stopType; - } - - @override - Widget build(BuildContext context) { - return InheritedWidgetBase( - data: _stopType, - child: widget.child, - ); - } - - void set(StopType type) { - setState(() { - _stopType = type; - }); - context.get().stopType = type; - } -} diff --git a/lib/providers/supported_locale_provider.dart b/lib/providers/supported_locale_provider.dart deleted file mode 100644 index caa7ced..0000000 --- a/lib/providers/supported_locale_provider.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:lightmeter/data/models/supported_locale.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; - -class SupportedLocaleProvider extends StatefulWidget { - final Widget child; - - const SupportedLocaleProvider({required this.child, super.key}); - - static SupportedLocaleProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - @override - State createState() => SupportedLocaleProviderState(); -} - -class SupportedLocaleProviderState extends State { - late final ValueNotifier valueListenable; - - @override - void initState() { - super.initState(); - valueListenable = ValueNotifier(context.get().locale); - } - - @override - void dispose() { - valueListenable.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: valueListenable, - builder: (_, value, child) => InheritedWidgetBase( - data: value, - child: child!, - ), - child: widget.child, - ); - } - - void setLocale(SupportedLocale locale) { - S.load(Locale(locale.intlName)).then((value) { - valueListenable.value = locale; - context.get().locale = locale; - }); - } -} diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart deleted file mode 100644 index 9773df1..0000000 --- a/lib/providers/theme_provider.dart +++ /dev/null @@ -1,257 +0,0 @@ -import 'package:dynamic_color/dynamic_color.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:lightmeter/data/models/dynamic_colors_state.dart'; -import 'package:lightmeter/data/models/theme_type.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/res/dimens.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; -import 'package:material_color_utilities/material_color_utilities.dart'; - -class ThemeProvider extends StatefulWidget { - final Widget child; - - const ThemeProvider({ - required this.child, - super.key, - }); - - static ThemeProviderState of(BuildContext context) { - return context.findAncestorStateOfType()!; - } - - static const primaryColorsList = [ - Color(0xfff44336), - Color(0xffe91e63), - Color(0xff9c27b0), - Color(0xff673ab7), - Color(0xff3f51b5), - Color(0xff2196f3), - Color(0xff03a9f4), - Color(0xff00bcd4), - Color(0xff009688), - Color(0xff4caf50), - Color(0xff8bc34a), - Color(0xffcddc39), - Color(0xffffeb3b), - Color(0xffffc107), - Color(0xffff9800), - Color(0xffff5722), - ]; - - @override - State createState() => ThemeProviderState(); -} - -class ThemeProviderState extends State with WidgetsBindingObserver { - UserPreferencesService get _prefs => context.get(); - - late final _themeTypeNotifier = ValueNotifier(_prefs.themeType); - late final _dynamicColorNotifier = ValueNotifier(_prefs.dynamicColor); - late final _primaryColorNotifier = ValueNotifier(_prefs.primaryColor); - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - } - - @override - void didChangePlatformBrightness() { - super.didChangePlatformBrightness(); - setState(() {}); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - _themeTypeNotifier.dispose(); - _dynamicColorNotifier.dispose(); - _primaryColorNotifier.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: _themeTypeNotifier, - builder: (_, themeType, __) => InheritedWidgetBase( - data: themeType, - child: ValueListenableBuilder( - valueListenable: _dynamicColorNotifier, - builder: (_, useDynamicColor, __) => _DynamicColorProvider( - useDynamicColor: useDynamicColor, - themeBrightness: _themeBrightness, - builder: (_, dynamicPrimaryColor) => ValueListenableBuilder( - valueListenable: _primaryColorNotifier, - builder: (_, primaryColor, __) => _ThemeDataProvider( - primaryColor: dynamicPrimaryColor ?? primaryColor, - brightness: _themeBrightness, - child: widget.child, - ), - ), - ), - ), - ), - ); - } - - void setThemeType(ThemeType themeType) { - _themeTypeNotifier.value = themeType; - _prefs.themeType = themeType; - } - - Brightness get _themeBrightness { - switch (_themeTypeNotifier.value) { - case ThemeType.light: - return Brightness.light; - case ThemeType.dark: - return Brightness.dark; - case ThemeType.systemDefault: - return SchedulerBinding.instance.platformDispatcher.platformBrightness; - } - } - - void setPrimaryColor(Color color) { - _primaryColorNotifier.value = color; - _prefs.primaryColor = color; - } - - void enableDynamicColor(bool enable) { - _dynamicColorNotifier.value = enable; - _prefs.dynamicColor = enable; - } -} - -class _DynamicColorProvider extends StatelessWidget { - final bool useDynamicColor; - final Brightness themeBrightness; - final Widget Function(BuildContext context, Color? primaryColor) builder; - - const _DynamicColorProvider({ - required this.useDynamicColor, - required this.themeBrightness, - required this.builder, - }); - - @override - Widget build(BuildContext context) { - return DynamicColorBuilder( - builder: (lightDynamic, darkDynamic) { - late final DynamicColorState state; - late final Color? dynamicPrimaryColor; - if (lightDynamic != null && darkDynamic != null) { - if (useDynamicColor) { - dynamicPrimaryColor = - (themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary; - state = DynamicColorState.enabled; - } else { - dynamicPrimaryColor = null; - state = DynamicColorState.disabled; - } - } else { - dynamicPrimaryColor = null; - state = DynamicColorState.unavailable; - } - return InheritedWidgetBase( - data: state, - child: builder(context, dynamicPrimaryColor), - ); - }, - ); - } -} - -class _ThemeDataProvider extends StatelessWidget { - final Color primaryColor; - final Brightness brightness; - final Widget child; - - const _ThemeDataProvider({ - required this.primaryColor, - required this.brightness, - required this.child, - }); - - @override - Widget build(BuildContext context) { - return InheritedWidgetBase( - data: _themeFromColorScheme(_colorSchemeFromColor()), - child: child, - ); - } - - ThemeData _themeFromColorScheme(ColorScheme scheme) { - return ThemeData( - useMaterial3: true, - brightness: scheme.brightness, - primaryColor: primaryColor, - colorScheme: scheme, - appBarTheme: AppBarTheme( - elevation: 4, - color: scheme.surface, - surfaceTintColor: scheme.surfaceTint, - ), - cardTheme: CardTheme( - clipBehavior: Clip.antiAlias, - color: scheme.surface, - elevation: 4, - margin: EdgeInsets.zero, - shadowColor: Colors.transparent, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)), - surfaceTintColor: scheme.surfaceTint, - ), - dialogBackgroundColor: scheme.surface, - dialogTheme: DialogTheme( - backgroundColor: scheme.surface, - surfaceTintColor: scheme.surfaceTint, - elevation: 6, - ), - dividerColor: scheme.outlineVariant, - dividerTheme: DividerThemeData( - color: scheme.outlineVariant, - space: 0, - ), - listTileTheme: ListTileThemeData( - style: ListTileStyle.list, - iconColor: scheme.onSurface, - textColor: scheme.onSurface, - ), - scaffoldBackgroundColor: scheme.surface, - ); - } - - ColorScheme _colorSchemeFromColor() { - final scheme = brightness == Brightness.light - ? Scheme.light(primaryColor.value) - : Scheme.dark(primaryColor.value); - - return ColorScheme( - brightness: brightness, - background: Color(scheme.background), - error: Color(scheme.error), - errorContainer: Color(scheme.errorContainer), - onBackground: Color(scheme.onBackground), - onError: Color(scheme.onError), - onErrorContainer: Color(scheme.onErrorContainer), - primary: Color(scheme.primary), - onPrimary: Color(scheme.onPrimary), - primaryContainer: Color(scheme.primaryContainer), - onPrimaryContainer: Color(scheme.onPrimaryContainer), - secondary: Color(scheme.secondary), - onSecondary: Color(scheme.onSecondary), - surface: Color.alphaBlend( - Color(scheme.primary).withOpacity(0.05), - Color(scheme.background), - ), - onSurface: Color(scheme.onSurface), - surfaceVariant: Color.alphaBlend( - Color(scheme.primary).withOpacity(0.5), - Color(scheme.background), - ), - onSurfaceVariant: Color(scheme.onSurfaceVariant), - outline: Color(scheme.outline), - outlineVariant: Color(scheme.outlineVariant), - ); - } -} diff --git a/lib/providers/user_preferences_provider.dart b/lib/providers/user_preferences_provider.dart new file mode 100644 index 0000000..af644d1 --- /dev/null +++ b/lib/providers/user_preferences_provider.dart @@ -0,0 +1,295 @@ +import 'package:dynamic_color/dynamic_color.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lightmeter/data/models/dynamic_colors_state.dart'; +import 'package:lightmeter/data/models/ev_source_type.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/data/models/supported_locale.dart'; +import 'package:lightmeter/data/models/theme_type.dart'; +import 'package:lightmeter/data/shared_prefs_service.dart'; +import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/services_provider.dart'; +import 'package:lightmeter/res/theme.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +class UserPreferencesProvider extends StatefulWidget { + final Widget child; + + const UserPreferencesProvider({required this.child, super.key}); + + static _UserPreferencesProviderState of(BuildContext context) { + return context.findAncestorStateOfType<_UserPreferencesProviderState>()!; + } + + static DynamicColorState dynamicColorStateOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.dynamicColorState).dynamicColorState; + } + + static EvSourceType evSourceTypeOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.evSourceType).evSourceType; + } + + static SupportedLocale localeOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.locale).locale; + } + + static MeteringScreenLayoutConfig meteringScreenConfigOf(BuildContext context) { + return context.findAncestorWidgetOfExactType<_MeteringScreenLayoutModel>()!.data; + } + + static bool meteringScreenFeatureOf(BuildContext context, MeteringScreenLayoutFeature feature) { + return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)! + .data[feature]!; + } + + static StopType stopTypeOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.stopType).stopType; + } + + static ThemeData themeOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.theme).theme; + } + + static ThemeType themeTypeOf(BuildContext context) { + return _inheritFromEnumsModel(context, _Aspect.themeType).themeType; + } + + static _UserPreferencesModel _inheritFromEnumsModel( + BuildContext context, + _Aspect aspect, + ) { + return InheritedModel.inheritFrom<_UserPreferencesModel>(context, aspect: aspect)!; + } + + @override + State createState() => _UserPreferencesProviderState(); +} + +class _UserPreferencesProviderState extends State + with WidgetsBindingObserver { + UserPreferencesService get userPreferencesService => + ServicesProvider.of(context).userPreferencesService; + + late bool dynamicColor = userPreferencesService.dynamicColor; + late EvSourceType evSourceType; + late MeteringScreenLayoutConfig meteringScreenLayout = + userPreferencesService.meteringScreenLayout; + late Color primaryColor = userPreferencesService.primaryColor; + late StopType stopType = userPreferencesService.stopType; + late SupportedLocale locale = userPreferencesService.locale; + late ThemeType themeType = userPreferencesService.themeType; + + @override + void initState() { + super.initState(); + evSourceType = userPreferencesService.evSourceType; + evSourceType = evSourceType == EvSourceType.sensor && + !ServicesProvider.of(context).environment.hasLightSensor + ? EvSourceType.camera + : evSourceType; + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangePlatformBrightness() { + super.didChangePlatformBrightness(); + setState(() {}); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DynamicColorBuilder( + builder: (lightDynamic, darkDynamic) { + late final DynamicColorState state; + late final Color? dynamicPrimaryColor; + if (lightDynamic != null && darkDynamic != null) { + if (dynamicColor) { + dynamicPrimaryColor = + (_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary; + state = DynamicColorState.enabled; + } else { + dynamicPrimaryColor = null; + state = DynamicColorState.disabled; + } + } else { + dynamicPrimaryColor = null; + state = DynamicColorState.unavailable; + } + return _UserPreferencesModel( + brightness: _themeBrightness, + dynamicColorState: state, + evSourceType: evSourceType, + locale: locale, + primaryColor: dynamicPrimaryColor ?? primaryColor, + stopType: stopType, + themeType: themeType, + child: _MeteringScreenLayoutModel( + data: meteringScreenLayout, + child: widget.child, + ), + ); + }, + ); + } + + void enableDynamicColor(bool enable) { + setState(() { + dynamicColor = enable; + }); + userPreferencesService.dynamicColor = enable; + } + + void toggleEvSourceType() { + if (!ServicesProvider.of(context).environment.hasLightSensor) { + return; + } + setState(() { + switch (evSourceType) { + case EvSourceType.camera: + evSourceType = EvSourceType.sensor; + case EvSourceType.sensor: + evSourceType = EvSourceType.camera; + } + }); + userPreferencesService.evSourceType = evSourceType; + } + + void setLocale(SupportedLocale locale) { + S.load(Locale(locale.intlName)).then((value) { + setState(() { + this.locale = locale; + }); + userPreferencesService.locale = locale; + }); + } + + void setMeteringScreenLayout(MeteringScreenLayoutConfig config) { + setState(() { + meteringScreenLayout = config; + }); + userPreferencesService.meteringScreenLayout = meteringScreenLayout; + } + + void setPrimaryColor(Color primaryColor) { + setState(() { + this.primaryColor = primaryColor; + }); + userPreferencesService.primaryColor = primaryColor; + } + + void setStopType(StopType stopType) { + setState(() { + this.stopType = stopType; + }); + userPreferencesService.stopType = stopType; + } + + void setThemeType(ThemeType themeType) { + setState(() { + this.themeType = themeType; + }); + userPreferencesService.themeType = themeType; + } + + Brightness get _themeBrightness { + switch (themeType) { + case ThemeType.light: + return Brightness.light; + case ThemeType.dark: + return Brightness.dark; + case ThemeType.systemDefault: + return SchedulerBinding.instance.platformDispatcher.platformBrightness; + } + } +} + +enum _Aspect { + dynamicColorState, + evSourceType, + locale, + stopType, + theme, + themeType, +} + +class _UserPreferencesModel extends InheritedModel<_Aspect> { + final DynamicColorState dynamicColorState; + final EvSourceType evSourceType; + final SupportedLocale locale; + final StopType stopType; + final ThemeType themeType; + + final Brightness _brightness; + final Color _primaryColor; + + const _UserPreferencesModel({ + required Brightness brightness, + required this.dynamicColorState, + required this.evSourceType, + required this.locale, + required Color primaryColor, + required this.stopType, + required this.themeType, + required super.child, + }) : _brightness = brightness, + _primaryColor = primaryColor; + + ThemeData get theme => themeFrom(_primaryColor, _brightness); + + @override + bool updateShouldNotify(_UserPreferencesModel oldWidget) { + return _brightness != oldWidget._brightness || + dynamicColorState != oldWidget.dynamicColorState || + evSourceType != oldWidget.evSourceType || + locale != oldWidget.locale || + _primaryColor != oldWidget._primaryColor || + stopType != oldWidget.stopType || + themeType != oldWidget.themeType; + } + + @override + bool updateShouldNotifyDependent( + _UserPreferencesModel oldWidget, + Set<_Aspect> dependencies, + ) { + return (dependencies.contains(_Aspect.dynamicColorState) && + dynamicColorState != oldWidget.dynamicColorState) || + (dependencies.contains(_Aspect.evSourceType) && evSourceType != oldWidget.evSourceType) || + (dependencies.contains(_Aspect.locale) && locale != oldWidget.locale) || + (dependencies.contains(_Aspect.stopType) && stopType != oldWidget.stopType) || + (dependencies.contains(_Aspect.theme) && + (_brightness != oldWidget._brightness || _primaryColor != oldWidget._primaryColor)) || + (dependencies.contains(_Aspect.themeType) && themeType != oldWidget.themeType); + } +} + +class _MeteringScreenLayoutModel extends InheritedModel { + final Map data; + + const _MeteringScreenLayoutModel({ + required this.data, + required super.child, + }); + + @override + bool updateShouldNotify(_MeteringScreenLayoutModel oldWidget) => oldWidget.data != data; + + @override + bool updateShouldNotifyDependent( + _MeteringScreenLayoutModel oldWidget, + Set dependencies, + ) { + for (final dependecy in dependencies) { + if (oldWidget.data[dependecy] != data[dependecy]) { + return true; + } + } + return false; + } +} diff --git a/lib/res/theme.dart b/lib/res/theme.dart new file mode 100644 index 0000000..a6320c1 --- /dev/null +++ b/lib/res/theme.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/res/dimens.dart'; +import 'package:material_color_utilities/material_color_utilities.dart'; + +const primaryColorsList = [ + Color(0xfff44336), + Color(0xffe91e63), + Color(0xff9c27b0), + Color(0xff673ab7), + Color(0xff3f51b5), + Color(0xff2196f3), + Color(0xff03a9f4), + Color(0xff00bcd4), + Color(0xff009688), + Color(0xff4caf50), + Color(0xff8bc34a), + Color(0xffcddc39), + Color(0xffffeb3b), + Color(0xffffc107), + Color(0xffff9800), + Color(0xffff5722), +]; + +ThemeData themeFrom(Color primaryColor, Brightness brightness) { + final scheme = _colorSchemeFromColor(primaryColor, brightness); + return ThemeData( + useMaterial3: true, + brightness: scheme.brightness, + primaryColor: primaryColor, + colorScheme: scheme, + appBarTheme: AppBarTheme( + elevation: 4, + color: scheme.surface, + surfaceTintColor: scheme.surfaceTint, + ), + cardTheme: CardTheme( + clipBehavior: Clip.antiAlias, + color: scheme.surface, + elevation: 4, + margin: EdgeInsets.zero, + shadowColor: Colors.transparent, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusL)), + surfaceTintColor: scheme.surfaceTint, + ), + dialogBackgroundColor: scheme.surface, + dialogTheme: DialogTheme( + backgroundColor: scheme.surface, + surfaceTintColor: scheme.surfaceTint, + elevation: 6, + ), + dividerColor: scheme.outlineVariant, + dividerTheme: DividerThemeData( + color: scheme.outlineVariant, + space: 0, + ), + listTileTheme: ListTileThemeData( + style: ListTileStyle.list, + iconColor: scheme.onSurface, + textColor: scheme.onSurface, + ), + scaffoldBackgroundColor: scheme.surface, + ); +} + +ColorScheme _colorSchemeFromColor(Color primaryColor, Brightness brightness) { + final scheme = brightness == Brightness.light + ? Scheme.light(primaryColor.value) + : Scheme.dark(primaryColor.value); + + return ColorScheme( + brightness: brightness, + background: Color(scheme.background), + error: Color(scheme.error), + errorContainer: Color(scheme.errorContainer), + onBackground: Color(scheme.onBackground), + onError: Color(scheme.onError), + onErrorContainer: Color(scheme.onErrorContainer), + primary: Color(scheme.primary), + onPrimary: Color(scheme.onPrimary), + primaryContainer: Color(scheme.primaryContainer), + onPrimaryContainer: Color(scheme.onPrimaryContainer), + secondary: Color(scheme.secondary), + onSecondary: Color(scheme.onSecondary), + surface: Color.alphaBlend( + Color(scheme.primary).withOpacity(0.05), + Color(scheme.background), + ), + onSurface: Color(scheme.onSurface), + surfaceVariant: Color.alphaBlend( + Color(scheme.primary).withOpacity(0.5), + Color(scheme.background), + ), + onSurfaceVariant: Color(scheme.onSurfaceVariant), + outline: Color(scheme.outline), + outlineVariant: Color(scheme.outlineVariant), + ); +} diff --git a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart index 9989494..54ea810 100644 --- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart +++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class MeteringBottomControls extends StatelessWidget { final double? ev; @@ -42,7 +42,7 @@ class MeteringBottomControls extends StatelessWidget { child: IconButton( onPressed: onSwitchEvSourceType, icon: Icon( - context.listen() != EvSourceType.camera + UserPreferencesProvider.evSourceTypeOf(context) != EvSourceType.camera ? Icons.camera_rear : Icons.wb_incandescent, ), diff --git a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart index 288c678..ecfcb42 100644 --- a/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart +++ b/lib/screens/metering/components/camera_container/components/camera_preview/widget_camera_preview.dart @@ -2,7 +2,7 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/platform_config.dart'; -import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view/widget_camera_view.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_preview/components/camera_view_placeholder/widget_placeholder_camera_view.dart'; @@ -38,7 +38,7 @@ class _CameraPreviewState extends State { alignment: Alignment.bottomCenter, children: [ CameraView(controller: widget.controller!), - if (MeteringScreenLayout.featureOf( + if (UserPreferencesProvider.meteringScreenFeatureOf( context, MeteringScreenLayoutFeature.histogram, )) diff --git a/lib/screens/metering/components/camera_container/provider_container_camera.dart b/lib/screens/metering/components/camera_container/provider_container_camera.dart index 344f573..0e27700 100644 --- a/lib/screens/metering/components/camera_container/provider_container_camera.dart +++ b/lib/screens/metering/components/camera_container/provider_container_camera.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/film.dart'; -import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/event_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/widget_container_camera.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/metering/flow_metering.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class CameraContainerProvider extends StatelessWidget { @@ -39,7 +38,7 @@ class CameraContainerProvider extends StatelessWidget { return BlocProvider( lazy: false, create: (context) => CameraContainerBloc( - context.get(), + MeteringInteractorProvider.of(context), context.read(), )..add(const RequestPermissionEvent()), child: CameraContainer( diff --git a/lib/screens/metering/components/camera_container/widget_container_camera.dart b/lib/screens/metering/components/camera_container/widget_container_camera.dart index e42991c..c510bc8 100644 --- a/lib/screens/metering/components/camera_container/widget_container_camera.dart +++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart @@ -7,7 +7,7 @@ import 'package:lightmeter/data/models/film.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/features.dart'; import 'package:lightmeter/platform_config.dart'; -import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/camera_container/bloc_container_camera.dart'; import 'package:lightmeter/screens/metering/components/camera_container/components/camera_controls/widget_camera_controls.dart'; @@ -114,14 +114,14 @@ class CameraContainer extends StatelessWidget { enabledFeaturesHeight += Dimens.readingContainerSingleValueHeight; enabledFeaturesHeight += Dimens.paddingS; } - if (MeteringScreenLayout.featureOf( + if (UserPreferencesProvider.meteringScreenFeatureOf( context, MeteringScreenLayoutFeature.extremeExposurePairs, )) { enabledFeaturesHeight += Dimens.readingContainerDoubleValueHeight; enabledFeaturesHeight += Dimens.paddingS; } - if (MeteringScreenLayout.featureOf( + if (UserPreferencesProvider.meteringScreenFeatureOf( context, MeteringScreenLayoutFeature.filmPicker, )) { diff --git a/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart b/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart index c7423fc..aa27504 100644 --- a/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart +++ b/lib/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/film.dart'; -import 'package:lightmeter/interactors/metering_interactor.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/components/light_sensor_container/bloc_container_light_sensor.dart'; import 'package:lightmeter/screens/metering/components/light_sensor_container/widget_container_light_sensor.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/metering/flow_metering.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class LightSensorContainerProvider extends StatelessWidget { @@ -38,7 +37,7 @@ class LightSensorContainerProvider extends StatelessWidget { return BlocProvider( lazy: false, create: (context) => LightSensorContainerBloc( - context.get(), + MeteringInteractorProvider.of(context), context.read(), ), child: LightSensorContainer( diff --git a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart index 0893305..d31380c 100644 --- a/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart +++ b/lib/screens/metering/components/shared/readings_container/widget_container_readings.dart @@ -5,11 +5,10 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/features.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart'; -import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/animated_dialog_picker/widget_picker_dialog_animated.dart'; import 'package:lightmeter/screens/metering/components/shared/readings_container/components/reading_value_container/widget_container_reading_value.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class ReadingsContainer extends StatelessWidget { @@ -43,7 +42,7 @@ class ReadingsContainer extends StatelessWidget { const _EquipmentProfilePicker(), const _InnerPadding(), ], - if (MeteringScreenLayout.featureOf( + if (UserPreferencesProvider.meteringScreenFeatureOf( context, MeteringScreenLayoutFeature.extremeExposurePairs, )) ...[ @@ -61,7 +60,7 @@ class ReadingsContainer extends StatelessWidget { ), const _InnerPadding(), ], - if (MeteringScreenLayout.featureOf( + if (UserPreferencesProvider.meteringScreenFeatureOf( context, MeteringScreenLayoutFeature.filmPicker, )) ...[ @@ -77,7 +76,7 @@ class ReadingsContainer extends StatelessWidget { Expanded( child: _IsoValuePicker( selectedValue: iso, - values: context.listen().isoValues, + values: EquipmentProfiles.selectedOf(context).isoValues, onChanged: onIsoChanged, ), ), @@ -85,7 +84,7 @@ class ReadingsContainer extends StatelessWidget { Expanded( child: _NdValuePicker( selectedValue: nd, - values: context.listen().ndValues, + values: EquipmentProfiles.selectedOf(context).ndValues, onChanged: onNdChanged, ), ), @@ -108,16 +107,16 @@ class _EquipmentProfilePicker extends StatelessWidget { return AnimatedDialogPicker( icon: Icons.camera, title: S.of(context).equipmentProfile, - selectedValue: context.listen(), - values: context.listen(), + selectedValue: EquipmentProfiles.selectedOf(context), + values: EquipmentProfiles.of(context), itemTitleBuilder: (_, value) => Text(value.id.isEmpty ? S.of(context).none : value.name), onChanged: EquipmentProfileProvider.of(context).setProfile, closedChild: ReadingValueContainer.singleValue( value: ReadingValue( label: S.of(context).equipmentProfile, - value: context.listen().id.isEmpty + value: EquipmentProfiles.selectedOf(context).id.isEmpty ? S.of(context).none - : context.listen().name, + : EquipmentProfiles.selectedOf(context).name, ), ), ); diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart index 1caef02..cca5675 100644 --- a/lib/screens/metering/flow_metering.dart +++ b/lib/screens/metering/flow_metering.dart @@ -1,17 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/data/caffeine_service.dart'; -import 'package:lightmeter/data/haptics_service.dart'; -import 'package:lightmeter/data/light_sensor_service.dart'; -import 'package:lightmeter/data/permissions_service.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/interactors/metering_interactor.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/screens/metering/bloc_metering.dart'; import 'package:lightmeter/screens/metering/communication/bloc_communication_metering.dart'; import 'package:lightmeter/screens/metering/components/shared/volume_keys_notifier/notifier_volume_keys.dart'; import 'package:lightmeter/screens/metering/screen_metering.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class MeteringFlow extends StatefulWidget { const MeteringFlow({super.key}); @@ -23,31 +17,45 @@ class MeteringFlow extends StatefulWidget { class _MeteringFlowState extends State { @override Widget build(BuildContext context) { - return InheritedWidgetBase( + return MeteringInteractorProvider( data: MeteringInteractor( - context.get(), - context.get(), - context.get(), - context.get(), - context.get(), - context.get(), + ServicesProvider.of(context).userPreferencesService, + ServicesProvider.of(context).caffeineService, + ServicesProvider.of(context).hapticsService, + ServicesProvider.of(context).permissionsService, + ServicesProvider.of(context).lightSensorService, + ServicesProvider.of(context).volumeEventsService, )..initialize(), - child: InheritedWidgetBase( - data: VolumeKeysNotifier(context.get()), - child: MultiBlocProvider( - providers: [ - BlocProvider(create: (_) => MeteringCommunicationBloc()), - BlocProvider( - create: (context) => MeteringBloc( - context.get(), - context.get(), - context.read(), - ), + child: MultiBlocProvider( + providers: [ + BlocProvider(create: (_) => MeteringCommunicationBloc()), + BlocProvider( + create: (context) => MeteringBloc( + MeteringInteractorProvider.of(context), + VolumeKeysNotifier(ServicesProvider.of(context).volumeEventsService), + context.read(), ), - ], - child: const MeteringScreen(), - ), + ), + ], + child: const MeteringScreen(), ), ); } } + +class MeteringInteractorProvider extends InheritedWidget { + final MeteringInteractor data; + + const MeteringInteractorProvider({ + required this.data, + required super.child, + super.key, + }); + + static MeteringInteractor of(BuildContext context) { + return context.findAncestorWidgetOfExactType()!.data; + } + + @override + bool updateShouldNotify(MeteringInteractorProvider oldWidget) => false; +} diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index bbfebfc..ba4dbea 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -6,15 +6,17 @@ import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/data/models/exposure_pair.dart'; import 'package:lightmeter/data/models/film.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; -import 'package:lightmeter/environment.dart'; -import 'package:lightmeter/providers/ev_source_type_provider.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:lightmeter/providers/services_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/metering/bloc_metering.dart'; import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart'; import 'package:lightmeter/screens/metering/components/camera_container/provider_container_camera.dart'; import 'package:lightmeter/screens/metering/components/light_sensor_container/provider_container_light_sensor.dart'; import 'package:lightmeter/screens/metering/event_metering.dart'; import 'package:lightmeter/screens/metering/state_metering.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/metering/utils/listener_metering_layout_feature.dart'; +import 'package:lightmeter/screens/metering/utils/listsner_equipment_profiles.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class MeteringScreen extends StatelessWidget { @@ -45,8 +47,8 @@ class MeteringScreen extends StatelessWidget { builder: (context, state) => MeteringBottomControlsProvider( ev: state is MeteringDataState ? state.ev : null, isMetering: state.isMetering, - onSwitchEvSourceType: context.get().hasLightSensor - ? EvSourceTypeProvider.of(context).toggleType + onSwitchEvSourceType: ServicesProvider.of(context).environment.hasLightSensor + ? UserPreferencesProvider.of(context).toggleEvSourceType : null, onMeasure: () => context.read().add(const MeasureEvent()), onSettings: () { @@ -71,12 +73,12 @@ class _InheritedListeners extends StatelessWidget { @override Widget build(BuildContext context) { - return InheritedWidgetListener( + return EquipmentProfileListener( onDidChangeDependencies: (value) { context.read().add(EquipmentProfileChangedEvent(value)); }, - child: InheritedModelAspectListener( - aspect: MeteringScreenLayoutFeature.filmPicker, + child: MeteringScreenLayoutFeatureListener( + feature: MeteringScreenLayoutFeature.filmPicker, onDidChangeDependencies: (value) { if (!value) context.read().add(const FilmChangedEvent(Film.other())); }, @@ -110,7 +112,8 @@ class _MeteringContainerBuidler extends StatelessWidget { final exposurePairs = ev != null ? buildExposureValues(context, ev!, film) : []; final fastest = exposurePairs.isNotEmpty ? exposurePairs.first : null; final slowest = exposurePairs.isNotEmpty ? exposurePairs.last : null; - return context.listen() == EvSourceType.camera + // Doubled build here when switching evSourceType. As new source bloc fires a new state on init + return UserPreferencesProvider.evSourceTypeOf(context) == EvSourceType.camera ? CameraContainerProvider( fastest: fastest, slowest: slowest, @@ -141,10 +144,10 @@ class _MeteringContainerBuidler extends StatelessWidget { } /// Depending on the `stopType` the exposure pairs list length is multiplied by 1,2 or 3 - final StopType stopType = context.listen(); + final StopType stopType = UserPreferencesProvider.stopTypeOf(context); final int evSteps = (ev * (stopType.index + 1)).round(); - final EquipmentProfile equipmentProfile = context.listen(); + final EquipmentProfile equipmentProfile = EquipmentProfiles.selectedOf(context); final List apertureValues = equipmentProfile.apertureValues.whereStopType(stopType); final List shutterSpeedValues = diff --git a/lib/screens/metering/utils/listener_metering_layout_feature.dart b/lib/screens/metering/utils/listener_metering_layout_feature.dart new file mode 100644 index 0000000..c245ec3 --- /dev/null +++ b/lib/screens/metering/utils/listener_metering_layout_feature.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; + +/// Listening to multiple dependencies at the same time causes firing an event for all dependencies +/// even though some of them didn't change: +/// ```dart +/// @override +/// void didChangeDependencies() { +/// super.didChangeDependencies(); +/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); +/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) { +/// _bloc.add(const FilmChangedEvent(Film.other())); +/// } +/// } +/// ``` +/// To overcome this issue I've decided to create a generic listener, +/// that will listen to each dependency separately. +class MeteringScreenLayoutFeatureListener extends StatefulWidget { + final MeteringScreenLayoutFeature feature; + final ValueChanged onDidChangeDependencies; + final Widget child; + + const MeteringScreenLayoutFeatureListener({ + required this.feature, + required this.onDidChangeDependencies, + required this.child, + super.key, + }); + + @override + State createState() => + _MeteringScreenLayoutFeatureListenerState(); +} + +class _MeteringScreenLayoutFeatureListenerState extends State { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + widget.onDidChangeDependencies( + UserPreferencesProvider.meteringScreenFeatureOf( + context, + widget.feature, + ), + ); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/screens/metering/utils/listsner_equipment_profiles.dart b/lib/screens/metering/utils/listsner_equipment_profiles.dart new file mode 100644 index 0000000..ec604ce --- /dev/null +++ b/lib/screens/metering/utils/listsner_equipment_profiles.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:lightmeter/providers/equipment_profile_provider.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; + +class EquipmentProfileListener extends StatefulWidget { + final ValueChanged onDidChangeDependencies; + final Widget child; + + const EquipmentProfileListener({ + required this.onDidChangeDependencies, + required this.child, + super.key, + }); + + @override + State createState() => _EquipmentProfileListenerState(); +} + +class _EquipmentProfileListenerState extends State { + @override + void didChangeDependencies() { + super.didChangeDependencies(); + widget.onDidChangeDependencies(EquipmentProfiles.selectedOf(context)); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart b/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart index 4c477f8..72bc1b5 100644 --- a/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart +++ b/lib/screens/settings/components/about/components/report_issue/widget_list_tile_report_issue.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:url_launcher/url_launcher.dart'; class ReportIssueListTile extends StatelessWidget { @@ -14,7 +13,7 @@ class ReportIssueListTile extends StatelessWidget { title: Text(S.of(context).reportIssue), onTap: () { launchUrl( - Uri.parse(context.get().issuesReportUrl), + Uri.parse(ServicesProvider.of(context).environment.issuesReportUrl), mode: LaunchMode.externalApplication, ); }, diff --git a/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart b/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart index 42a73e8..4327332 100644 --- a/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart +++ b/lib/screens/settings/components/about/components/source_code/widget_list_tile_source_code.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:url_launcher/url_launcher.dart'; class SourceCodeListTile extends StatelessWidget { @@ -14,7 +13,7 @@ class SourceCodeListTile extends StatelessWidget { title: Text(S.of(context).sourceCode), onTap: () { launchUrl( - Uri.parse(context.get().sourceCodeUrl), + Uri.parse(ServicesProvider.of(context).environment.sourceCodeUrl), mode: LaunchMode.externalApplication, ); }, diff --git a/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart b/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart index b12d0d4..b0a4391 100644 --- a/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart +++ b/lib/screens/settings/components/about/components/write_email/widget_list_tile_write_email.dart @@ -1,8 +1,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; -import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:url_launcher/url_launcher.dart'; class WriteEmailListTile extends StatelessWidget { @@ -14,7 +13,7 @@ class WriteEmailListTile extends StatelessWidget { leading: const Icon(Icons.email), title: Text(S.of(context).writeEmail), onTap: () { - final email = context.get().contactEmail; + final email = ServicesProvider.of(context).environment.contactEmail; final mailToUrl = Uri.parse('mailto:$email?subject=M3 Lightmeter'); canLaunchUrl(mailToUrl).then((canLaunch) { if (canLaunch) { diff --git a/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart b/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart index 4509d20..8d9bcfb 100644 --- a/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart +++ b/lib/screens/settings/components/general/components/caffeine/provider_list_tile_caffeine.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/general/components/caffeine/bloc_list_tile_caffeine.dart'; import 'package:lightmeter/screens/settings/components/general/components/caffeine/widget_list_tile_caffeine.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; class CaffeineListTileProvider extends StatelessWidget { const CaffeineListTileProvider({super.key}); @@ -12,7 +11,7 @@ class CaffeineListTileProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CaffeineListTileBloc(context.get()), + create: (context) => CaffeineListTileBloc(SettingsInteractorProvider.of(context)), child: const CaffeineListTile(), ); } diff --git a/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart b/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart index beb0462..34cdeab 100644 --- a/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart +++ b/lib/screens/settings/components/general/components/haptics/provider_list_tile_haptics.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/general/components/haptics/bloc_list_tile_haptics.dart'; import 'package:lightmeter/screens/settings/components/general/components/haptics/widget_list_tile_haptics.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; class HapticsListTileProvider extends StatelessWidget { const HapticsListTileProvider({super.key}); @@ -12,7 +11,7 @@ class HapticsListTileProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => HapticsListTileBloc(context.get()), + create: (context) => HapticsListTileBloc(SettingsInteractorProvider.of(context)), child: const HapticsListTile(), ); } diff --git a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart index 381f286..663c634 100644 --- a/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart +++ b/lib/screens/settings/components/general/components/language/widget_list_tile_language.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/supported_locale_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class LanguageListTile extends StatelessWidget { const LanguageListTile({super.key}); @@ -13,20 +12,20 @@ class LanguageListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.language), title: Text(S.of(context).language), - trailing: Text(context.listen().localizedName), + trailing: Text(UserPreferencesProvider.localeOf(context).localizedName), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.language, title: S.of(context).chooseLanguage, - selectedValue: context.get(), + selectedValue: UserPreferencesProvider.localeOf(context), values: SupportedLocale.values, titleAdapter: (context, value) => value.localizedName, ), ).then((value) { if (value != null) { - SupportedLocaleProvider.of(context).setLocale(value); + UserPreferencesProvider.of(context).setLocale(value); } }); }, diff --git a/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart index 790ad4f..f99505f 100644 --- a/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart +++ b/lib/screens/settings/components/general/components/volume_actions/provider_list_tile_volume_actions.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/general/components/volume_actions/bloc_list_tile_volume_actions.dart'; import 'package:lightmeter/screens/settings/components/general/components/volume_actions/widget_list_tile_volume_actions.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; class VolumeActionsListTileProvider extends StatelessWidget { const VolumeActionsListTileProvider({super.key}); @@ -12,7 +11,7 @@ class VolumeActionsListTileProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => VolumeActionsListTileBloc(context.get()), + create: (context) => VolumeActionsListTileBloc(SettingsInteractorProvider.of(context)), child: const VolumeActionsListTile(), ); } diff --git a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart index a95ec1b..a26a6cf 100644 --- a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart +++ b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/bloc_dialog_calibration.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; class CalibrationDialogProvider extends StatelessWidget { const CalibrationDialogProvider({super.key}); @@ -12,7 +11,7 @@ class CalibrationDialogProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CalibrationDialogBloc(context.get()), + create: (context) => CalibrationDialogBloc(SettingsInteractorProvider.of(context)), child: const CalibrationDialog(), ); } diff --git a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart index 4c91c87..69a4f00 100644 --- a/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart +++ b/lib/screens/settings/components/metering/components/calibration/components/calibration_dialog/widget_dialog_calibration.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/bloc_dialog_calibration.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/event_dialog_calibration.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/state_dialog_calibration.dart'; import 'package:lightmeter/screens/shared/centered_slider/widget_slider_centered.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:lightmeter/utils/to_string_signed.dart'; class CalibrationDialog extends StatelessWidget { @@ -15,7 +14,7 @@ class CalibrationDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final bool hasLightSensor = context.get().hasLightSensor; + final bool hasLightSensor = ServicesProvider.of(context).environment.hasLightSensor; return AlertDialog( icon: const Icon(Icons.settings_brightness), titlePadding: Dimens.dialogIconTitlePadding, diff --git a/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart b/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart index e591e4b..61690c4 100644 --- a/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart +++ b/lib/screens/settings/components/metering/components/calibration/widget_list_tile_calibration.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/metering/components/calibration/components/calibration_dialog/provider_dialog_calibration.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; class CalibrationListTile extends StatelessWidget { const CalibrationListTile({super.key}); @@ -15,8 +14,8 @@ class CalibrationListTile extends StatelessWidget { onTap: () { showDialog( context: context, - builder: (_) => InheritedWidgetBase( - data: context.get(), + builder: (_) => SettingsInteractorProvider( + data: SettingsInteractorProvider.of(context), child: const CalibrationDialogProvider(), ), ); diff --git a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart index 4872d79..3120b38 100644 --- a/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart +++ b/lib/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/screen_equipment_profile.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart'; + import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_container/widget_container_equipment_profile.dart'; import 'package:lightmeter/screens/settings/components/metering/components/equipment_profiles/components/equipment_profile_screen/components/equipment_profile_name_dialog/widget_dialog_equipment_profile_name.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class EquipmentProfilesScreen extends StatefulWidget { @@ -19,13 +19,12 @@ class _EquipmentProfilesScreenState extends State { static const maxProfiles = 5 + 1; // replace with a constant from iap late List> profileContainersKeys = []; - int get profilesCount => context.listen().length; + int get profilesCount => EquipmentProfiles.of(context).length; @override - void initState() { - super.initState(); - profileContainersKeys = context - .get() + void didChangeDependencies() { + super.didChangeDependencies(); + profileContainersKeys = EquipmentProfiles.of(context) .map((e) => GlobalKey(debugLabel: e.id)) .toList(); } @@ -58,14 +57,14 @@ class _EquipmentProfilesScreenState extends State { ), child: EquipmentProfileContainer( key: profileContainersKeys[index], - data: context.listen()[index], + data: EquipmentProfiles.of(context)[index], onExpand: () => _keepExpandedAt(index), onUpdate: (profileData) => _updateProfileAt(profileData, index), onDelete: () => _removeProfileAt(index), ), ) : const SizedBox.shrink(), - childCount: profileContainersKeys.length, + childCount: profilesCount, ), ), ], @@ -79,7 +78,6 @@ class _EquipmentProfilesScreenState extends State { ).then((value) { if (value != null) { EquipmentProfileProvider.of(context).addProfile(value); - profileContainersKeys.add(GlobalKey()); } }); } @@ -89,8 +87,7 @@ class _EquipmentProfilesScreenState extends State { } void _removeProfileAt(int index) { - EquipmentProfileProvider.of(context).deleteProfile(context.listen()[index]); - profileContainersKeys.removeAt(index); + EquipmentProfileProvider.of(context).deleteProfile(EquipmentProfiles.of(context)[index]); } void _keepExpandedAt(int index) { diff --git a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart index 2a27a56..1bcf6bc 100644 --- a/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart +++ b/lib/screens/settings/components/metering/components/fractional_stops/widget_list_tile_fractional_stops.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/stop_type_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class StopTypeListTile extends StatelessWidget { @@ -13,20 +12,20 @@ class StopTypeListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.straighten), title: Text(S.of(context).fractionalStops), - trailing: Text(_typeToString(context, context.listen())), + trailing: Text(_typeToString(context, UserPreferencesProvider.stopTypeOf(context))), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.straighten, title: S.of(context).showFractionalStops, - selectedValue: context.get(), + selectedValue: UserPreferencesProvider.stopTypeOf(context), values: StopType.values, titleAdapter: _typeToString, ), ).then((value) { if (value != null) { - StopTypeProvider.of(context).set(value); + UserPreferencesProvider.of(context).setStopType(value); } }); }, diff --git a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart b/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart index d43f011..c60abab 100644 --- a/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart +++ b/lib/screens/settings/components/metering/components/metering_screen_layout/components/meterins_screen_layout_features_dialog/widget_dialog_metering_screen_layout_features.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/metering_screen_layout_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { @@ -14,7 +14,7 @@ class MeteringScreenLayoutFeaturesDialog extends StatefulWidget { class _MeteringScreenLayoutFeaturesDialogState extends State { late final _features = - MeteringScreenLayoutConfig.from(MeteringScreenLayout.of(context, listen: false)); + MeteringScreenLayoutConfig.from(UserPreferencesProvider.meteringScreenConfigOf(context)); @override Widget build(BuildContext context) { @@ -56,7 +56,7 @@ class _MeteringScreenLayoutFeaturesDialogState extends State() == DynamicColorState.enabled, - onChanged: ThemeProvider.of(context).enableDynamicColor, + value: UserPreferencesProvider.dynamicColorStateOf(context) == DynamicColorState.enabled, + onChanged: UserPreferencesProvider.of(context).enableDynamicColor, contentPadding: const EdgeInsets.symmetric(horizontal: Dimens.paddingM), ); } diff --git a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart index 434336a..380faa1 100644 --- a/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart +++ b/lib/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/theme_provider.dart'; import 'package:lightmeter/res/dimens.dart'; +import 'package:lightmeter/res/theme.dart'; import 'package:lightmeter/screens/shared/filled_circle/widget_circle_filled.dart'; class PrimaryColorDialogPicker extends StatefulWidget { @@ -38,9 +38,9 @@ class _PrimaryColorDialogPickerState extends State { padding: EdgeInsets.zero, child: Row( children: List.generate( - ThemeProvider.primaryColorsList.length, + primaryColorsList.length, (index) { - final color = ThemeProvider.primaryColorsList[index]; + final color = primaryColorsList[index]; return Padding( padding: EdgeInsets.only(left: index == 0 ? 0 : Dimens.paddingS), child: _SelectableColorItem( diff --git a/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart b/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart index afc1a7b..44de22e 100644 --- a/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart +++ b/lib/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/dynamic_colors_state.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/theme_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/settings/components/theme/components/primary_color/components/primary_color_picker_dialog/widget_dialog_picker_primary_color.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class PrimaryColorListTile extends StatelessWidget { const PrimaryColorListTile({super.key}); @override Widget build(BuildContext context) { - if (context.listen() == DynamicColorState.enabled) { + if (UserPreferencesProvider.dynamicColorStateOf(context) == DynamicColorState.enabled) { return Opacity( opacity: Dimens.disabledOpacity, child: IgnorePointer( @@ -31,7 +30,7 @@ class PrimaryColorListTile extends StatelessWidget { builder: (_) => const PrimaryColorDialogPicker(), ).then((value) { if (value != null) { - ThemeProvider.of(context).setPrimaryColor(value); + UserPreferencesProvider.of(context).setPrimaryColor(value); } }); }, diff --git a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart index 46e1b86..8a95161 100644 --- a/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart +++ b/lib/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/providers/theme_provider.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class ThemeTypeListTile extends StatelessWidget { const ThemeTypeListTile({super.key}); @@ -13,20 +12,20 @@ class ThemeTypeListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.brightness_6), title: Text(S.of(context).theme), - trailing: Text(_typeToString(context, context.listen())), + trailing: Text(_typeToString(context, UserPreferencesProvider.themeTypeOf(context))), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.brightness_6, title: S.of(context).chooseTheme, - selectedValue: context.get(), + selectedValue: UserPreferencesProvider.themeTypeOf(context), values: ThemeType.values, titleAdapter: _typeToString, ), ).then((value) { if (value != null) { - ThemeProvider.of(context).setThemeType(value); + UserPreferencesProvider.of(context).setThemeType(value); } }); }, diff --git a/lib/screens/settings/components/theme/widget_settings_section_theme.dart b/lib/screens/settings/components/theme/widget_settings_section_theme.dart index d137469..ee03c54 100644 --- a/lib/screens/settings/components/theme/widget_settings_section_theme.dart +++ b/lib/screens/settings/components/theme/widget_settings_section_theme.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/dynamic_colors_state.dart'; import 'package:lightmeter/generated/l10n.dart'; +import 'package:lightmeter/providers/user_preferences_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/settings_section/widget_settings_section.dart'; import 'package:lightmeter/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart'; import 'package:lightmeter/screens/settings/components/theme/components/primary_color/widget_list_tile_primary_color.dart'; import 'package:lightmeter/screens/settings/components/theme/components/theme_type/widget_list_tile_theme_type.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class ThemeSettingsSection extends StatelessWidget { const ThemeSettingsSection({super.key}); @@ -17,7 +17,7 @@ class ThemeSettingsSection extends StatelessWidget { children: [ const ThemeTypeListTile(), const PrimaryColorListTile(), - if (context.get() != DynamicColorState.unavailable) + if (UserPreferencesProvider.dynamicColorStateOf(context) != DynamicColorState.unavailable) const DynamicColorListTile(), ], ); diff --git a/lib/screens/settings/flow_settings.dart b/lib/screens/settings/flow_settings.dart index 3195c25..f3c8156 100644 --- a/lib/screens/settings/flow_settings.dart +++ b/lib/screens/settings/flow_settings.dart @@ -1,25 +1,38 @@ import 'package:flutter/material.dart'; -import 'package:lightmeter/data/caffeine_service.dart'; -import 'package:lightmeter/data/haptics_service.dart'; -import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/data/volume_events_service.dart'; import 'package:lightmeter/interactors/settings_interactor.dart'; +import 'package:lightmeter/providers/services_provider.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class SettingsFlow extends StatelessWidget { const SettingsFlow({super.key}); @override Widget build(BuildContext context) { - return InheritedWidgetBase( + return SettingsInteractorProvider( data: SettingsInteractor( - context.get(), - context.get(), - context.get(), - context.get(), + ServicesProvider.of(context).userPreferencesService, + ServicesProvider.of(context).caffeineService, + ServicesProvider.of(context).hapticsService, + ServicesProvider.of(context).volumeEventsService, ), child: const SettingsScreen(), ); } } + +class SettingsInteractorProvider extends InheritedWidget { + final SettingsInteractor data; + + const SettingsInteractorProvider({ + required this.data, + required super.child, + super.key, + }); + + static SettingsInteractor of(BuildContext context) { + return context.findAncestorWidgetOfExactType()!.data; + } + + @override + bool updateShouldNotify(SettingsInteractorProvider oldWidget) => false; +} diff --git a/lib/screens/settings/screen_settings.dart b/lib/screens/settings/screen_settings.dart index 3c745bd..9332d3f 100644 --- a/lib/screens/settings/screen_settings.dart +++ b/lib/screens/settings/screen_settings.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/components/about/widget_settings_section_about.dart'; import 'package:lightmeter/screens/settings/components/general/widget_settings_section_general.dart'; import 'package:lightmeter/screens/settings/components/metering/widget_settings_section_metering.dart'; import 'package:lightmeter/screens/settings/components/theme/widget_settings_section_theme.dart'; +import 'package:lightmeter/screens/settings/flow_settings.dart'; import 'package:lightmeter/screens/shared/sliver_screen/screen_sliver.dart'; -import 'package:lightmeter/utils/inherited_generics.dart'; class SettingsScreen extends StatefulWidget { const SettingsScreen({super.key}); @@ -19,12 +18,12 @@ class _SettingsScreenState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - context.get().disableVolumeHandling(); + SettingsInteractorProvider.of(context).disableVolumeHandling(); } @override void deactivate() { - context.get().restoreVolumeHandling(); + SettingsInteractorProvider.of(context).restoreVolumeHandling(); super.deactivate(); } diff --git a/lib/utils/inherited_generics.dart b/lib/utils/inherited_generics.dart deleted file mode 100644 index 5f71ec6..0000000 --- a/lib/utils/inherited_generics.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'package:flutter/widgets.dart'; - -/// Listening to multiple dependencies at the same time causes firing an event for all dependencies -/// even though some of them didn't change: -/// ```dart -/// @override -/// void didChangeDependencies() { -/// super.didChangeDependencies(); -/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); -/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) { -/// _bloc.add(const FilmChangedEvent(Film.other())); -/// } -/// } -/// ``` -/// To overcome this issue I've decided to create a generic listener, -/// that will listen to each dependency separately. -class InheritedWidgetListener extends StatefulWidget { - final ValueChanged onDidChangeDependencies; - final Widget child; - - const InheritedWidgetListener({ - required this.onDidChangeDependencies, - required this.child, - super.key, - }); - - @override - State> createState() => _InheritedWidgetListenerState(); -} - -class _InheritedWidgetListenerState extends State> { - @override - void didChangeDependencies() { - super.didChangeDependencies(); - widget.onDidChangeDependencies(context.listen()); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -} - -class InheritedWidgetBase extends InheritedWidget { - final T data; - - const InheritedWidgetBase({ - required this.data, - required super.child, - super.key, - }); - - static T of(BuildContext context, {bool listen = true}) { - if (listen) { - return context.dependOnInheritedWidgetOfExactType>()!.data; - } else { - return context.findAncestorWidgetOfExactType>()!.data; - } - } - - @override - bool updateShouldNotify(InheritedWidgetBase oldWidget) => true; -} - -extension InheritedWidgetBaseContext on BuildContext { - T get() { - return InheritedWidgetBase.of(this, listen: false); - } - - T listen() { - return InheritedWidgetBase.of(this); - } -} - -/// Listening to multiple dependencies at the same time causes firing an event for all dependencies -/// even though some of them didn't change: -/// ```dart -/// @override -/// void didChangeDependencies() { -/// super.didChangeDependencies(); -/// _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); -/// if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) { -/// _bloc.add(const FilmChangedEvent(Film.other())); -/// } -/// } -/// ``` -/// To overcome this issue I've decided to create a generic listener, -/// that will listen to each dependency separately. -class InheritedModelAspectListener extends StatefulWidget { - final A aspect; - final ValueChanged onDidChangeDependencies; - final Widget child; - - const InheritedModelAspectListener({ - required this.aspect, - required this.onDidChangeDependencies, - required this.child, - super.key, - }); - - @override - State> createState() => - _InheritedModelAspectListenerState(); -} - -class _InheritedModelAspectListenerState - extends State> { - @override - void didChangeDependencies() { - super.didChangeDependencies(); - widget.onDidChangeDependencies(context.listenModelFeature(widget.aspect)); - } - - @override - Widget build(BuildContext context) { - return widget.child; - } -} - -class InheritedModelBase extends InheritedModel { - final Map data; - - const InheritedModelBase({ - required this.data, - required super.child, - super.key, - }); - - static Map of(BuildContext context, {bool listen = true}) { - if (listen) { - return context.dependOnInheritedWidgetOfExactType>()!.data; - } else { - return context.findAncestorWidgetOfExactType>()!.data; - } - } - - static T featureOf(BuildContext context, A aspect) { - return InheritedModel.inheritFrom>(context, aspect: aspect)! - .data[aspect]!; - } - - @override - bool updateShouldNotify(InheritedModelBase oldWidget) => true; - - @override - bool updateShouldNotifyDependent( - InheritedModelBase oldWidget, - Set dependencies, - ) { - for (final dependecy in dependencies) { - if (oldWidget.data[dependecy] != data[dependecy]) { - return true; - } - } - return false; - } -} - -extension InheritedModelBaseContext on BuildContext { - Map getModel() { - return InheritedModelBase.of(this, listen: false); - } - - Map listenModel() { - return InheritedModelBase.of(this); - } - - T listenModelFeature(A aspect) { - return InheritedModelBase.featureOf(this, aspect); - } -} diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 514629f..692cb81 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -6,7 +6,7 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/supported_locale.dart'; import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; -import 'package:lightmeter/providers/theme_provider.dart'; +import 'package:lightmeter/res/theme.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:mocktail/mocktail.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -348,13 +348,13 @@ void main() { group('primaryColor', () { test('get default', () { when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)).thenReturn(null); - expect(service.primaryColor, ThemeProvider.primaryColorsList[5]); + expect(service.primaryColor, primaryColorsList[5]); }); test('get', () { when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)) .thenReturn(0xff9c27b0); - expect(service.primaryColor, ThemeProvider.primaryColorsList[2]); + expect(service.primaryColor, primaryColorsList[2]); }); test('set', () {