From cdf7372913df09eb196d8a48cef7b921be408924 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Sun, 4 Jun 2023 13:04:04 +0200 Subject: [PATCH] ML-77 Redundant vibrations (#76) * wip * `MeteringScreenLayout = InheritedModelBase` * removed `Provider` from providers folder * wip * Update pubspec.yaml * `context.get()` * `context.get()` * `context.get()` * typo * fixed `MeteringScreenLayout` * fixed redundant vibrations --- lib/application.dart | 100 +++------- lib/data/permissions_service.dart | 2 + lib/providers.dart | 74 ++++++++ lib/providers/equipment_profile_provider.dart | 63 ++----- lib/providers/ev_source_type_provider.dart | 16 +- .../metering_screen_layout_provider.dart | 46 ++--- lib/providers/stop_type_provider.dart | 10 +- lib/providers/supported_locale_provider.dart | 12 +- lib/providers/theme_provider.dart | 29 ++- lib/screens/metering/bloc_metering.dart | 62 ++++--- .../widget_bottom_controls.dart | 4 +- .../provider_container_camera.dart | 3 +- .../widget_container_camera.dart | 4 +- .../provider_container_light_sensor.dart | 3 +- .../widget_container_readings.dart | 17 +- lib/screens/metering/flow_metering.dart | 22 +-- lib/screens/metering/screen_metering.dart | 109 ++++++----- .../widget_list_tile_report_issue.dart | 4 +- .../widget_list_tile_source_code.dart | 4 +- .../widget_list_tile_write_email.dart | 4 +- .../caffeine/provider_list_tile_caffeine.dart | 3 +- .../haptics/provider_list_tile_haptics.dart | 3 +- .../language/widget_list_tile_language.dart | 6 +- .../provider_dialog_calibration.dart | 3 +- .../widget_dialog_calibration.dart | 3 +- .../widget_list_tile_calibration.dart | 6 +- .../screen_equipment_profile.dart | 10 +- .../widget_list_tile_fractional_stops.dart | 6 +- .../widget_list_tile_dynamic_color.dart | 4 +- .../widget_list_tile_primary_color.dart | 5 +- .../widget_list_tile_theme_type.dart | 6 +- .../theme/widget_settings_section_theme.dart | 5 +- lib/screens/settings/flow_settings.dart | 12 +- lib/utils/inherited_generics.dart | 171 ++++++++++++++++++ pubspec.yaml | 1 - 35 files changed, 505 insertions(+), 327 deletions(-) create mode 100644 lib/providers.dart create mode 100644 lib/utils/inherited_generics.dart diff --git a/lib/application.dart b/lib/application.dart index 39b2de2..3880a08 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -1,26 +1,13 @@ -import 'dart:io'; - 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/environment.dart'; import 'package:lightmeter/generated/l10n.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/providers.dart'; import 'package:lightmeter/screens/metering/flow_metering.dart'; import 'package:lightmeter/screens/settings/flow_settings.dart'; -import 'package:provider/provider.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class Application extends StatelessWidget { final Environment env; @@ -29,65 +16,32 @@ class Application extends StatelessWidget { @override Widget build(BuildContext context) { - return FutureBuilder( - future: Future.wait([ - SharedPreferences.getInstance(), - if (Platform.isAndroid) const LightSensorService().hasSensor() else Future.value(false), - ]), - builder: (_, snapshot) { - if (snapshot.data != null) { - return MultiProvider( - providers: [ - Provider.value(value: env.copyWith(hasLightSensor: snapshot.data![1] as bool)), - Provider( - create: (_) => UserPreferencesService(snapshot.data![0] as SharedPreferences), - ), - Provider(create: (_) => const CaffeineService()), - Provider(create: (_) => const HapticsService()), - Provider(create: (_) => PermissionsService()), - Provider(create: (_) => const LightSensorService()), - ], - child: MeteringScreenLayoutProvider( - child: StopTypeProvider( - child: EquipmentProfileProvider( - child: EvSourceTypeProvider( - child: SupportedLocaleProvider( - child: ThemeProvider( - builder: (context, _) => _AnnotatedRegionWrapper( - child: MaterialApp( - theme: context.watch(), - locale: Locale(context.watch().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(), - }, - ), - ), - ), - ), - ), + 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!, ), + initialRoute: "metering", + routes: { + "metering": (context) => const MeteringFlow(), + "settings": (context) => const SettingsFlow(), + }, ), - ), - ); - } else if (snapshot.error != null) { - return Center(child: Text(snapshot.error!.toString())); - } else { - return const SizedBox.shrink(); - } - }, + ) + : const SizedBox(), ); } } @@ -100,7 +54,7 @@ class _AnnotatedRegionWrapper extends StatelessWidget { @override Widget build(BuildContext context) { final systemIconsBrightness = ThemeData.estimateBrightnessForColor( - context.watch().colorScheme.onSurface, + context.listen().colorScheme.onSurface, ); return AnnotatedRegion( value: SystemUiOverlayStyle( diff --git a/lib/data/permissions_service.dart b/lib/data/permissions_service.dart index fe731b4..6d0e64c 100644 --- a/lib/data/permissions_service.dart +++ b/lib/data/permissions_service.dart @@ -1,6 +1,8 @@ import 'package:permission_handler/permission_handler.dart'; class PermissionsService { + const PermissionsService(); + Future checkCameraPermission() async => Permission.camera.status; Future requestCameraPermission() async => Permission.camera.request(); diff --git a/lib/providers.dart b/lib/providers.dart new file mode 100644 index 0000000..7805e69 --- /dev/null +++ b/lib/providers.dart @@ -0,0 +1,74 @@ +import 'dart:io'; + +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/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: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(), + if (Platform.isAndroid) const LightSensorService().hasSensor() else Future.value(false), + ]), + 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(), + child: InheritedWidgetBase( + data: const CaffeineService(), + child: InheritedWidgetBase( + data: const HapticsService(), + 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 fea496e..c5ef82d 100644 --- a/lib/providers/equipment_profile_provider.dart +++ b/lib/providers/equipment_profile_provider.dart @@ -1,9 +1,12 @@ 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'; -import 'package:provider/provider.dart'; import 'package:uuid/uuid.dart'; +typedef EquipmentProfiles = List; +typedef EquipmentProfile = EquipmentProfileData; + class EquipmentProfileProvider extends StatefulWidget { final Widget child; @@ -33,7 +36,7 @@ class EquipmentProfileProviderState extends State { EquipmentProfileData get _selectedProfile => _customProfiles.firstWhere( (e) => e.id == _selectedId, orElse: () { - context.read().selectedEquipmentProfileId = _defaultProfile.id; + context.get().selectedEquipmentProfileId = _defaultProfile.id; return _defaultProfile; }, ); @@ -41,15 +44,15 @@ class EquipmentProfileProviderState extends State { @override void initState() { super.initState(); - _selectedId = context.read().selectedEquipmentProfileId; - _customProfiles = context.read().equipmentProfiles; + _selectedId = context.get().selectedEquipmentProfileId; + _customProfiles = context.get().equipmentProfiles; } @override Widget build(BuildContext context) { - return EquipmentProfiles( - profiles: [_defaultProfile] + _customProfiles, - child: EquipmentProfile( + return InheritedWidgetBase>( + data: [_defaultProfile] + _customProfiles, + child: InheritedWidgetBase( data: _selectedProfile, child: widget.child, ), @@ -60,7 +63,7 @@ class EquipmentProfileProviderState extends State { setState(() { _selectedId = data.id; }); - context.read().selectedEquipmentProfileId = _selectedProfile.id; + context.get().selectedEquipmentProfileId = _selectedProfile.id; } /// Creates a default equipment profile @@ -92,49 +95,7 @@ class EquipmentProfileProviderState extends State { } void _refreshSavedProfiles() { - context.read().equipmentProfiles = _customProfiles; + context.get().equipmentProfiles = _customProfiles; setState(() {}); } } - -class EquipmentProfiles extends InheritedWidget { - final List profiles; - - const EquipmentProfiles({ - required this.profiles, - required super.child, - super.key, - }); - - static List of(BuildContext context, {bool listen = true}) { - if (listen) { - return context.dependOnInheritedWidgetOfExactType()!.profiles; - } else { - return context.findAncestorWidgetOfExactType()!.profiles; - } - } - - @override - bool updateShouldNotify(EquipmentProfiles oldWidget) => true; -} - -class EquipmentProfile extends InheritedWidget { - final EquipmentProfileData data; - - const EquipmentProfile({ - required this.data, - required super.child, - super.key, - }); - - static EquipmentProfileData of(BuildContext context, {bool listen = true}) { - if (listen) { - return context.dependOnInheritedWidgetOfExactType()!.data; - } else { - return context.findAncestorWidgetOfExactType()!.data; - } - } - - @override - bool updateShouldNotify(EquipmentProfile oldWidget) => true; -} diff --git a/lib/providers/ev_source_type_provider.dart b/lib/providers/ev_source_type_provider.dart index 83657e2..aed873b 100644 --- a/lib/providers/ev_source_type_provider.dart +++ b/lib/providers/ev_source_type_provider.dart @@ -2,7 +2,7 @@ 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:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class EvSourceTypeProvider extends StatefulWidget { final Widget child; @@ -23,9 +23,9 @@ class EvSourceTypeProviderState extends State { @override void initState() { super.initState(); - final evSourceType = context.read().evSourceType; + final evSourceType = context.get().evSourceType; valueListenable = ValueNotifier( - evSourceType == EvSourceType.sensor && !context.read().hasLightSensor + evSourceType == EvSourceType.sensor && !context.get().hasLightSensor ? EvSourceType.camera : evSourceType, ); @@ -41,9 +41,9 @@ class EvSourceTypeProviderState extends State { Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: valueListenable, - builder: (_, value, child) => Provider.value( - value: value, - child: child, + builder: (_, value, child) => InheritedWidgetBase( + data: value, + child: child!, ), child: widget.child, ); @@ -52,12 +52,12 @@ class EvSourceTypeProviderState extends State { void toggleType() { switch (valueListenable.value) { case EvSourceType.camera: - if (context.read().hasLightSensor) { + if (context.get().hasLightSensor) { valueListenable.value = EvSourceType.sensor; } case EvSourceType.sensor: valueListenable.value = EvSourceType.camera; } - context.read().evSourceType = valueListenable.value; + context.get().evSourceType = valueListenable.value; } } diff --git a/lib/providers/metering_screen_layout_provider.dart b/lib/providers/metering_screen_layout_provider.dart index 83e3773..405c1a7 100644 --- a/lib/providers/metering_screen_layout_provider.dart +++ b/lib/providers/metering_screen_layout_provider.dart @@ -1,7 +1,7 @@ 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:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class MeteringScreenLayoutProvider extends StatefulWidget { final Widget child; @@ -18,12 +18,12 @@ class MeteringScreenLayoutProvider extends StatefulWidget { class MeteringScreenLayoutProviderState extends State { late final MeteringScreenLayoutConfig _config = - context.read().meteringScreenLayout; + context.get().meteringScreenLayout; @override Widget build(BuildContext context) { - return MeteringScreenLayout( - config: MeteringScreenLayoutConfig.from(_config), + return InheritedModelBase( + data: MeteringScreenLayoutConfig.from(_config), child: widget.child, ); } @@ -38,45 +38,23 @@ class MeteringScreenLayoutProviderState extends State().meteringScreenLayout = _config; + context.get().meteringScreenLayout = _config; } } -class MeteringScreenLayout extends InheritedModel { - final MeteringScreenLayoutConfig config; - - const MeteringScreenLayout({ - required this.config, - required super.child, - super.key, - }); +typedef _MeteringScreenLayoutModel = InheritedModelBase; +extension MeteringScreenLayout on InheritedModelBase { static MeteringScreenLayoutConfig of(BuildContext context, {bool listen = true}) { if (listen) { - return context.dependOnInheritedWidgetOfExactType()!.config; + return context.dependOnInheritedWidgetOfExactType<_MeteringScreenLayoutModel>()!.data; } else { - return context.findAncestorWidgetOfExactType()!.config; + return context.findAncestorWidgetOfExactType<_MeteringScreenLayoutModel>()!.data; } } - static bool featureStatusOf(BuildContext context, MeteringScreenLayoutFeature feature) { - return InheritedModel.inheritFrom(context, aspect: feature)! - .config[feature]!; - } - - @override - bool updateShouldNotify(MeteringScreenLayout oldWidget) => true; - - @override - bool updateShouldNotifyDependent( - MeteringScreenLayout oldWidget, - Set dependencies, - ) { - for (final dependecy in dependencies) { - if (oldWidget.config[dependecy] != config[dependecy]) { - return true; - } - } - return false; + static bool featureOf(BuildContext context, MeteringScreenLayoutFeature aspect) { + return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: aspect)! + .data[aspect]!; } } diff --git a/lib/providers/stop_type_provider.dart b/lib/providers/stop_type_provider.dart index a6bbe1a..690f65f 100644 --- a/lib/providers/stop_type_provider.dart +++ b/lib/providers/stop_type_provider.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; -import 'package:provider/provider.dart'; class StopTypeProvider extends StatefulWidget { - final Widget? child; + final Widget child; - const StopTypeProvider({this.child, super.key}); + const StopTypeProvider({required this.child, super.key}); static StopTypeProviderState of(BuildContext context) { return context.findAncestorStateOfType()!; @@ -28,8 +28,8 @@ class StopTypeProviderState extends State { @override Widget build(BuildContext context) { - return Provider.value( - value: _stopType, + return InheritedWidgetBase( + data: _stopType, child: widget.child, ); } diff --git a/lib/providers/supported_locale_provider.dart b/lib/providers/supported_locale_provider.dart index 32ee912..caa7ced 100644 --- a/lib/providers/supported_locale_provider.dart +++ b/lib/providers/supported_locale_provider.dart @@ -2,7 +2,7 @@ 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:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class SupportedLocaleProvider extends StatefulWidget { final Widget child; @@ -23,7 +23,7 @@ class SupportedLocaleProviderState extends State { @override void initState() { super.initState(); - valueListenable = ValueNotifier(context.read().locale); + valueListenable = ValueNotifier(context.get().locale); } @override @@ -36,9 +36,9 @@ class SupportedLocaleProviderState extends State { Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: valueListenable, - builder: (_, value, child) => Provider.value( - value: value, - child: child, + builder: (_, value, child) => InheritedWidgetBase( + data: value, + child: child!, ), child: widget.child, ); @@ -47,7 +47,7 @@ class SupportedLocaleProviderState extends State { void setLocale(SupportedLocale locale) { S.load(Locale(locale.intlName)).then((value) { valueListenable.value = locale; - context.read().locale = locale; + context.get().locale = locale; }); } } diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme_provider.dart index d2f1f37..9773df1 100644 --- a/lib/providers/theme_provider.dart +++ b/lib/providers/theme_provider.dart @@ -5,15 +5,14 @@ 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'; -import 'package:provider/provider.dart'; - class ThemeProvider extends StatefulWidget { - final TransitionBuilder? builder; + final Widget child; const ThemeProvider({ - this.builder, + required this.child, super.key, }); @@ -45,7 +44,7 @@ class ThemeProvider extends StatefulWidget { } class ThemeProviderState extends State with WidgetsBindingObserver { - UserPreferencesService get _prefs => context.read(); + UserPreferencesService get _prefs => context.get(); late final _themeTypeNotifier = ValueNotifier(_prefs.themeType); late final _dynamicColorNotifier = ValueNotifier(_prefs.dynamicColor); @@ -76,8 +75,8 @@ class ThemeProviderState extends State with WidgetsBindingObserve Widget build(BuildContext context) { return ValueListenableBuilder( valueListenable: _themeTypeNotifier, - builder: (_, themeType, __) => Provider.value( - value: themeType, + builder: (_, themeType, __) => InheritedWidgetBase( + data: themeType, child: ValueListenableBuilder( valueListenable: _dynamicColorNotifier, builder: (_, useDynamicColor, __) => _DynamicColorProvider( @@ -88,7 +87,7 @@ class ThemeProviderState extends State with WidgetsBindingObserve builder: (_, primaryColor, __) => _ThemeDataProvider( primaryColor: dynamicPrimaryColor ?? primaryColor, brightness: _themeBrightness, - builder: widget.builder, + child: widget.child, ), ), ), @@ -154,8 +153,8 @@ class _DynamicColorProvider extends StatelessWidget { dynamicPrimaryColor = null; state = DynamicColorState.unavailable; } - return Provider.value( - value: state, + return InheritedWidgetBase( + data: state, child: builder(context, dynamicPrimaryColor), ); }, @@ -166,19 +165,19 @@ class _DynamicColorProvider extends StatelessWidget { class _ThemeDataProvider extends StatelessWidget { final Color primaryColor; final Brightness brightness; - final TransitionBuilder? builder; + final Widget child; const _ThemeDataProvider({ required this.primaryColor, required this.brightness, - required this.builder, + required this.child, }); @override Widget build(BuildContext context) { - return Provider.value( - value: _themeFromColorScheme(_colorSchemeFromColor()), - builder: builder, + return InheritedWidgetBase( + data: _themeFromColorScheme(_colorSchemeFromColor()), + child: child, ); } diff --git a/lib/screens/metering/bloc_metering.dart b/lib/screens/metering/bloc_metering.dart index 1b52882..34d5a3c 100644 --- a/lib/screens/metering/bloc_metering.dart +++ b/lib/screens/metering/bloc_metering.dart @@ -40,7 +40,7 @@ class MeteringBloc extends Bloc { this.stopType, ) : super( MeteringDataState( - ev: 0.0, + ev: null, film: _meteringInteractor.film, iso: _meteringInteractor.iso, nd: _meteringInteractor.ndFilter, @@ -77,55 +77,75 @@ class MeteringBloc extends Bloc { } void _onStopTypeChanged(StopTypeChangedEvent event, Emitter emit) { - stopType = event.stopType; - _updateMeasurements(); + if (stopType != event.stopType) { + stopType = event.stopType; + _updateMeasurements(); + } } void _onEquipmentProfileChanged(EquipmentProfileChangedEvent event, Emitter emit) { _equipmentProfileData = event.equipmentProfileData; + bool willUpdateMeasurements = false; /// Update selected ISO value, if selected equipment profile /// doesn't contain currently selected value if (!event.equipmentProfileData.isoValues.any((v) => _iso.value == v.value)) { _meteringInteractor.iso = event.equipmentProfileData.isoValues.first; _iso = event.equipmentProfileData.isoValues.first; + willUpdateMeasurements &= true; } /// The same for ND filter if (!event.equipmentProfileData.ndValues.any((v) => _nd.value == v.value)) { _meteringInteractor.ndFilter = event.equipmentProfileData.ndValues.first; _nd = event.equipmentProfileData.ndValues.first; + willUpdateMeasurements &= true; } - _updateMeasurements(); + if (willUpdateMeasurements) { + _updateMeasurements(); + } } void _onFilmChanged(FilmChangedEvent event, Emitter emit) { - if (_iso.value != event.data.iso) { - final newIso = IsoValue.values.firstWhere( - (e) => e.value == event.data.iso, - orElse: () => _iso, - ); - add(IsoChangedEvent(newIso)); + if (_film.name != event.data.name) { + _film = event.data; + _meteringInteractor.film = event.data; + + /// If user selects 'Other' film we preserve currently selected ISO + /// and therefore only discard reciprocity formula + if (_iso.value != event.data.iso && event.data != const Film.other()) { + final newIso = IsoValue.values.firstWhere( + (e) => e.value == event.data.iso, + orElse: () => _iso, + ); + _meteringInteractor.iso = newIso; + _iso = newIso; + } + + _updateMeasurements(); } - _film = event.data; - _meteringInteractor.film = event.data; - _updateMeasurements(); } void _onIsoChanged(IsoChangedEvent event, Emitter emit) { - if (event.isoValue.value != _film.iso) { - _film = Film.values.first; + /// Discard currently selected film even if ISO is the same, + /// because, for example, Fomapan 400 and any Ilford 400 + /// have different reciprocity formulas + _film = Film.values.first; + + if (_iso != event.isoValue) { + _meteringInteractor.iso = event.isoValue; + _iso = event.isoValue; + _updateMeasurements(); } - _meteringInteractor.iso = event.isoValue; - _iso = event.isoValue; - _updateMeasurements(); } void _onNdChanged(NdChangedEvent event, Emitter emit) { - _meteringInteractor.ndFilter = event.ndValue; - _nd = event.ndValue; - _updateMeasurements(); + if (_nd != event.ndValue) { + _meteringInteractor.ndFilter = event.ndValue; + _nd = event.ndValue; + _updateMeasurements(); + } } void _onMeasure(MeasureEvent _, Emitter emit) { 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 13a3892..96cd7b6 100644 --- a/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart +++ b/lib/screens/metering/components/bottom_controls/widget_bottom_controls.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/data/models/ev_source_type.dart'; import 'package:lightmeter/res/dimens.dart'; import 'package:lightmeter/screens/metering/components/bottom_controls/components/measure_button/widget_button_measure.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class MeteringBottomControls extends StatelessWidget { final double? ev; @@ -44,7 +44,7 @@ class MeteringBottomControls extends StatelessWidget { child: IconButton( onPressed: onSwitchEvSourceType, icon: Icon( - context.watch() != EvSourceType.camera + context.listen() != EvSourceType.camera ? Icons.camera_rear : Icons.wb_incandescent, ), 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 c334ecd..81357f7 100644 --- a/lib/screens/metering/components/camera_container/provider_container_camera.dart +++ b/lib/screens/metering/components/camera_container/provider_container_camera.dart @@ -6,6 +6,7 @@ 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/widget_container_camera.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class CameraContainerProvider extends StatelessWidget { @@ -37,7 +38,7 @@ class CameraContainerProvider extends StatelessWidget { return BlocProvider( lazy: false, create: (context) => CameraContainerBloc( - context.read(), + context.get(), context.read(), ), 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 d107ebd..2f8ef45 100644 --- a/lib/screens/metering/components/camera_container/widget_container_camera.dart +++ b/lib/screens/metering/components/camera_container/widget_container_camera.dart @@ -56,14 +56,14 @@ class CameraContainer extends StatelessWidget { topBarOverflow += Dimens.readingContainerSingleValueHeight; topBarOverflow += Dimens.paddingS; } - if (MeteringScreenLayout.featureStatusOf( + if (MeteringScreenLayout.featureOf( context, MeteringScreenLayoutFeature.extremeExposurePairs, )) { topBarOverflow += Dimens.readingContainerDoubleValueHeight; topBarOverflow += Dimens.paddingS; } - if (MeteringScreenLayout.featureStatusOf( + if (MeteringScreenLayout.featureOf( 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 ab2b626..c7423fc 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 @@ -6,6 +6,7 @@ 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:m3_lightmeter_resources/m3_lightmeter_resources.dart'; class LightSensorContainerProvider extends StatelessWidget { @@ -37,7 +38,7 @@ class LightSensorContainerProvider extends StatelessWidget { return BlocProvider( lazy: false, create: (context) => LightSensorContainerBloc( - context.read(), + context.get(), 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 9edf2e7..09f8bce 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 @@ -9,6 +9,7 @@ import 'package:lightmeter/providers/metering_screen_layout_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 { @@ -42,7 +43,7 @@ class ReadingsContainer extends StatelessWidget { const _EquipmentProfilePicker(), const _InnerPadding(), ], - if (MeteringScreenLayout.featureStatusOf( + if (MeteringScreenLayout.featureOf( context, MeteringScreenLayoutFeature.extremeExposurePairs, )) ...[ @@ -60,7 +61,7 @@ class ReadingsContainer extends StatelessWidget { ), const _InnerPadding(), ], - if (MeteringScreenLayout.featureStatusOf( + if (MeteringScreenLayout.featureOf( context, MeteringScreenLayoutFeature.filmPicker, )) ...[ @@ -76,7 +77,7 @@ class ReadingsContainer extends StatelessWidget { Expanded( child: _IsoValuePicker( selectedValue: iso, - values: EquipmentProfile.of(context).isoValues, + values: context.listen().isoValues, onChanged: onIsoChanged, ), ), @@ -84,7 +85,7 @@ class ReadingsContainer extends StatelessWidget { Expanded( child: _NdValuePicker( selectedValue: nd, - values: EquipmentProfile.of(context).ndValues, + values: context.listen().ndValues, onChanged: onNdChanged, ), ), @@ -107,16 +108,16 @@ class _EquipmentProfilePicker extends StatelessWidget { return AnimatedDialogPicker( icon: Icons.camera, title: S.of(context).equipmentProfile, - selectedValue: EquipmentProfile.of(context), - values: EquipmentProfiles.of(context), + selectedValue: context.listen(), + values: context.listen(), 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: EquipmentProfile.of(context).id.isEmpty + value: context.listen().id.isEmpty ? S.of(context).none - : EquipmentProfile.of(context).name, + : context.listen().name, ), ), ); diff --git a/lib/screens/metering/flow_metering.dart b/lib/screens/metering/flow_metering.dart index 3f830c2..3cd2f33 100644 --- a/lib/screens/metering/flow_metering.dart +++ b/lib/screens/metering/flow_metering.dart @@ -10,8 +10,8 @@ import 'package:lightmeter/providers/equipment_profile_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/screen_metering.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; -import 'package:provider/provider.dart'; class MeteringFlow extends StatefulWidget { const MeteringFlow({super.key}); @@ -23,13 +23,13 @@ class MeteringFlow extends StatefulWidget { class _MeteringFlowState extends State { @override Widget build(BuildContext context) { - return Provider( - create: (context) => MeteringInteractor( - context.read(), - context.read(), - context.read(), - context.read(), - context.read(), + return InheritedWidgetBase( + data: MeteringInteractor( + context.get(), + context.get(), + context.get(), + context.get(), + context.get(), ), child: MultiBlocProvider( providers: [ @@ -37,9 +37,9 @@ class _MeteringFlowState extends State { BlocProvider( create: (context) => MeteringBloc( context.read(), - context.read(), - EquipmentProfile.of(context, listen: false), - context.read(), + context.get(), + context.get(), + context.get(), ), ), ], diff --git a/lib/screens/metering/screen_metering.dart b/lib/screens/metering/screen_metering.dart index f3c6b22..29c7813 100644 --- a/lib/screens/metering/screen_metering.dart +++ b/lib/screens/metering/screen_metering.dart @@ -7,70 +7,83 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.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/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:m3_lightmeter_resources/m3_lightmeter_resources.dart'; -class MeteringScreen extends StatefulWidget { +class MeteringScreen extends StatelessWidget { const MeteringScreen({super.key}); @override - State createState() => _MeteringScreenState(); + Widget build(BuildContext context) { + return _InheritedListeners( + child: Scaffold( + backgroundColor: Theme.of(context).colorScheme.background, + body: Column( + children: [ + Expanded( + child: BlocBuilder( + builder: (_, state) => _MeteringContainerBuidler( + fastest: state is MeteringDataState ? state.fastest : null, + slowest: state is MeteringDataState ? state.slowest : null, + exposurePairs: state is MeteringDataState ? state.exposurePairs : [], + film: state.film, + iso: state.iso, + nd: state.nd, + onFilmChanged: (value) => + context.read().add(FilmChangedEvent(value)), + onIsoChanged: (value) => context.read().add(IsoChangedEvent(value)), + onNdChanged: (value) => context.read().add(NdChangedEvent(value)), + ), + ), + ), + BlocBuilder( + builder: (context, state) => MeteringBottomControlsProvider( + ev: state is MeteringDataState ? state.ev : null, + isMetering: + state is LoadingState || state is MeteringDataState && state.continuousMetering, + hasError: state is MeteringDataState && state.hasError, + onSwitchEvSourceType: context.get().hasLightSensor + ? EvSourceTypeProvider.of(context).toggleType + : null, + onMeasure: () => context.read().add(const MeasureEvent()), + onSettings: () => Navigator.pushNamed(context, 'settings'), + ), + ), + ], + ), + ), + ); + } } -class _MeteringScreenState extends State { - MeteringBloc get _bloc => context.read(); +class _InheritedListeners extends StatelessWidget { + final Widget child; - @override - void didChangeDependencies() { - super.didChangeDependencies(); - _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); - _bloc.add(StopTypeChangedEvent(context.watch())); - if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) { - _bloc.add(const FilmChangedEvent(Film.other())); - } - } + const _InheritedListeners({required this.child}); @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - body: Column( - children: [ - Expanded( - child: BlocBuilder( - builder: (_, state) => _MeteringContainerBuidler( - fastest: state is MeteringDataState ? state.fastest : null, - slowest: state is MeteringDataState ? state.slowest : null, - exposurePairs: state is MeteringDataState ? state.exposurePairs : [], - film: state.film, - iso: state.iso, - nd: state.nd, - onFilmChanged: (value) => _bloc.add(FilmChangedEvent(value)), - onIsoChanged: (value) => _bloc.add(IsoChangedEvent(value)), - onNdChanged: (value) => _bloc.add(NdChangedEvent(value)), - ), - ), - ), - BlocBuilder( - builder: (context, state) => MeteringBottomControlsProvider( - ev: state is MeteringDataState ? state.ev : null, - isMetering: - state is LoadingState || state is MeteringDataState && state.continuousMetering, - hasError: state is MeteringDataState && state.hasError, - onSwitchEvSourceType: context.read().hasLightSensor - ? EvSourceTypeProvider.of(context).toggleType - : null, - onMeasure: () => _bloc.add(const MeasureEvent()), - onSettings: () => Navigator.pushNamed(context, 'settings'), - ), - ), - ], + return InheritedWidgetListener( + onDidChangeDependencies: (value) { + context.read().add(EquipmentProfileChangedEvent(value)); + }, + child: InheritedWidgetListener( + onDidChangeDependencies: (value) { + context.read().add(StopTypeChangedEvent(value)); + }, + child: InheritedModelAspectListener( + aspect: MeteringScreenLayoutFeature.filmPicker, + onDidChangeDependencies: (value) { + if (!value) context.read().add(const FilmChangedEvent(Film.other())); + }, + child: child, + ), ), ); } @@ -101,7 +114,7 @@ class _MeteringContainerBuidler extends StatelessWidget { @override Widget build(BuildContext context) { - return context.watch() == EvSourceType.camera + return context.listen() == EvSourceType.camera ? CameraContainerProvider( fastest: fastest, slowest: slowest, 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 f7e4e4b..4c477f8 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,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:url_launcher/url_launcher.dart'; class ReportIssueListTile extends StatelessWidget { @@ -14,7 +14,7 @@ class ReportIssueListTile extends StatelessWidget { title: Text(S.of(context).reportIssue), onTap: () { launchUrl( - Uri.parse(context.read().issuesReportUrl), + Uri.parse(context.get().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 39199a9..42a73e8 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,7 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:url_launcher/url_launcher.dart'; class SourceCodeListTile extends StatelessWidget { @@ -14,7 +14,7 @@ class SourceCodeListTile extends StatelessWidget { title: Text(S.of(context).sourceCode), onTap: () { launchUrl( - Uri.parse(context.read().sourceCodeUrl), + Uri.parse(context.get().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 1641e9c..b12d0d4 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 @@ -2,7 +2,7 @@ import 'package:clipboard/clipboard.dart'; import 'package:flutter/material.dart'; import 'package:lightmeter/environment.dart'; import 'package:lightmeter/generated/l10n.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; import 'package:url_launcher/url_launcher.dart'; class WriteEmailListTile extends StatelessWidget { @@ -14,7 +14,7 @@ class WriteEmailListTile extends StatelessWidget { leading: const Icon(Icons.email), title: Text(S.of(context).writeEmail), onTap: () { - final email = context.read().contactEmail; + final email = context.get().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 269fa20..4509d20 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 @@ -4,6 +4,7 @@ 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'; class CaffeineListTileProvider extends StatelessWidget { const CaffeineListTileProvider({super.key}); @@ -11,7 +12,7 @@ class CaffeineListTileProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CaffeineListTileBloc(context.read()), + create: (context) => CaffeineListTileBloc(context.get()), 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 894172e..beb0462 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 @@ -4,6 +4,7 @@ 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'; class HapticsListTileProvider extends StatelessWidget { const HapticsListTileProvider({super.key}); @@ -11,7 +12,7 @@ class HapticsListTileProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => HapticsListTileBloc(context.read()), + create: (context) => HapticsListTileBloc(context.get()), 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 4bea398..381f286 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 @@ -3,7 +3,7 @@ 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/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class LanguageListTile extends StatelessWidget { const LanguageListTile({super.key}); @@ -13,14 +13,14 @@ class LanguageListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.language), title: Text(S.of(context).language), - trailing: Text(context.watch().localizedName), + trailing: Text(context.listen().localizedName), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.language, title: S.of(context).chooseLanguage, - selectedValue: context.read(), + selectedValue: context.get(), values: SupportedLocale.values, titleAdapter: (context, value) => value.localizedName, ), 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 a968dfe..a95ec1b 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 @@ -4,6 +4,7 @@ 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'; class CalibrationDialogProvider extends StatelessWidget { const CalibrationDialogProvider({super.key}); @@ -11,7 +12,7 @@ class CalibrationDialogProvider extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => CalibrationDialogBloc(context.read()), + create: (context) => CalibrationDialogBloc(context.get()), 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 7a4b5fa..4c91c87 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 @@ -7,6 +7,7 @@ import 'package:lightmeter/screens/settings/components/metering/components/calib 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 { @@ -14,7 +15,7 @@ class CalibrationDialog extends StatelessWidget { @override Widget build(BuildContext context) { - final bool hasLightSensor = context.read().hasLightSensor; + final bool hasLightSensor = context.get().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 7781371..e591e4b 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 @@ -2,7 +2,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:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class CalibrationListTile extends StatelessWidget { const CalibrationListTile({super.key}); @@ -15,8 +15,8 @@ class CalibrationListTile extends StatelessWidget { onTap: () { showDialog( context: context, - builder: (_) => Provider.value( - value: context.read(), + builder: (_) => InheritedWidgetBase( + data: context.get(), 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 04a9be1..25ca59a 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 @@ -5,6 +5,7 @@ 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 { @@ -18,12 +19,13 @@ class _EquipmentProfilesScreenState extends State { static const maxProfiles = 5 + 1; // replace with a constant from iap late List> profileContainersKeys = []; - int get profilesCount => EquipmentProfiles.of(context).length; + int get profilesCount => context.listen().length; @override void initState() { super.initState(); - profileContainersKeys = EquipmentProfiles.of(context, listen: false) + profileContainersKeys = context + .get() .map((e) => GlobalKey(debugLabel: e.id)) .toList(); } @@ -56,7 +58,7 @@ class _EquipmentProfilesScreenState extends State { ), child: EquipmentProfileContainer( key: profileContainersKeys[index], - data: EquipmentProfiles.of(context)[index], + data: context.listen()[index], onExpand: () => _keepExpandedAt(index), onUpdate: (profileData) => _updateProfileAt(profileData, index), onDelete: () => _removeProfileAt(index), @@ -87,7 +89,7 @@ class _EquipmentProfilesScreenState extends State { } void _removeProfileAt(int index) { - EquipmentProfileProvider.of(context).deleteProfile(EquipmentProfiles.of(context)[index]); + EquipmentProfileProvider.of(context).deleteProfile(context.listen()[index]); profileContainersKeys.removeAt(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 a21ec94..2a27a56 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 @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/stop_type_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'; -import 'package:provider/provider.dart'; class StopTypeListTile extends StatelessWidget { const StopTypeListTile({super.key}); @@ -13,14 +13,14 @@ class StopTypeListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.straighten), title: Text(S.of(context).fractionalStops), - trailing: Text(_typeToString(context, context.watch())), + trailing: Text(_typeToString(context, context.listen())), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.straighten, title: S.of(context).showFractionalStops, - selectedValue: context.read(), + selectedValue: context.get(), values: StopType.values, titleAdapter: _typeToString, ), diff --git a/lib/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart b/lib/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart index db8e7f0..f15186e 100644 --- a/lib/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart +++ b/lib/screens/settings/components/theme/components/dynamic_color/widget_list_tile_dynamic_color.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/utils/inherited_generics.dart'; class DynamicColorListTile extends StatelessWidget { const DynamicColorListTile({super.key}); @@ -12,7 +12,7 @@ class DynamicColorListTile extends StatelessWidget { return SwitchListTile( secondary: const Icon(Icons.colorize), title: Text(S.of(context).dynamicColor), - value: context.watch() == DynamicColorState.enabled, + value: context.listen() == DynamicColorState.enabled, onChanged: ThemeProvider.of(context).enableDynamicColor, ); } 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 6b6c0d0..afc1a7b 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,18 +1,17 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/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.watch() == DynamicColorState.enabled) { + if (context.listen() == DynamicColorState.enabled) { return Opacity( opacity: Dimens.disabledOpacity, child: IgnorePointer( 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 3c43224..46e1b86 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 @@ -3,7 +3,7 @@ import 'package:lightmeter/data/models/theme_type.dart'; import 'package:lightmeter/generated/l10n.dart'; import 'package:lightmeter/providers/theme_provider.dart'; import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class ThemeTypeListTile extends StatelessWidget { const ThemeTypeListTile({super.key}); @@ -13,14 +13,14 @@ class ThemeTypeListTile extends StatelessWidget { return ListTile( leading: const Icon(Icons.brightness_6), title: Text(S.of(context).theme), - trailing: Text(_typeToString(context, context.watch())), + trailing: Text(_typeToString(context, context.listen())), onTap: () { showDialog( context: context, builder: (_) => DialogPicker( icon: Icons.brightness_6, title: S.of(context).chooseTheme, - selectedValue: context.read(), + selectedValue: context.get(), values: ThemeType.values, titleAdapter: _typeToString, ), 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 8a9bbfa..d137469 100644 --- a/lib/screens/settings/components/theme/widget_settings_section_theme.dart +++ b/lib/screens/settings/components/theme/widget_settings_section_theme.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:lightmeter/data/models/dynamic_colors_state.dart'; import 'package:lightmeter/generated/l10n.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}); @@ -18,7 +17,7 @@ class ThemeSettingsSection extends StatelessWidget { children: [ const ThemeTypeListTile(), const PrimaryColorListTile(), - if (context.read() != DynamicColorState.unavailable) + if (context.get() != DynamicColorState.unavailable) const DynamicColorListTile(), ], ); diff --git a/lib/screens/settings/flow_settings.dart b/lib/screens/settings/flow_settings.dart index 9e757b4..5b9d5b3 100644 --- a/lib/screens/settings/flow_settings.dart +++ b/lib/screens/settings/flow_settings.dart @@ -4,18 +4,18 @@ import 'package:lightmeter/data/haptics_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/interactors/settings_interactor.dart'; import 'package:lightmeter/screens/settings/screen_settings.dart'; -import 'package:provider/provider.dart'; +import 'package:lightmeter/utils/inherited_generics.dart'; class SettingsFlow extends StatelessWidget { const SettingsFlow({super.key}); @override Widget build(BuildContext context) { - return Provider( - create: (context) => SettingsInteractor( - context.read(), - context.read(), - context.read(), + return InheritedWidgetBase( + data: SettingsInteractor( + context.get(), + context.get(), + context.get(), ), child: const SettingsScreen(), ); diff --git a/lib/utils/inherited_generics.dart b/lib/utils/inherited_generics.dart new file mode 100644 index 0000000..5f71ec6 --- /dev/null +++ b/lib/utils/inherited_generics.dart @@ -0,0 +1,171 @@ +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/pubspec.yaml b/pubspec.yaml index d1f0acd..5c7c3d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,6 @@ dependencies: material_color_utilities: 0.2.0 package_info_plus: 4.0.1 permission_handler: 10.2.0 - provider: 6.0.4 shared_preferences: 2.1.1 url_launcher: 6.1.11 uuid: 3.0.7