MeteringScreenLayout = InheritedModelBase<MeteringScreenLayoutFeature, bool>

This commit is contained in:
Vadim 2023-05-17 10:50:33 +02:00
parent 6f094d6432
commit 2c85a3fddc
5 changed files with 132 additions and 94 deletions

View file

@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/data/shared_prefs_service.dart'; import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/utils/inherited_generics.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
typedef MeteringScreenLayout = InheritedModelBase<MeteringScreenLayoutFeature, bool>;
class MeteringScreenLayoutProvider extends StatefulWidget { class MeteringScreenLayoutProvider extends StatefulWidget {
final Widget child; final Widget child;
@ -22,8 +25,8 @@ class MeteringScreenLayoutProviderState extends State<MeteringScreenLayoutProvid
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MeteringScreenLayout( return InheritedModelBase<MeteringScreenLayoutFeature, bool>(
config: MeteringScreenLayoutConfig.from(_config), data: MeteringScreenLayoutConfig.from(_config),
child: widget.child, child: widget.child,
); );
} }
@ -41,42 +44,3 @@ class MeteringScreenLayoutProviderState extends State<MeteringScreenLayoutProvid
context.read<UserPreferencesService>().meteringScreenLayout = _config; context.read<UserPreferencesService>().meteringScreenLayout = _config;
} }
} }
class MeteringScreenLayout extends InheritedModel<MeteringScreenLayoutFeature> {
final MeteringScreenLayoutConfig config;
const MeteringScreenLayout({
required this.config,
required super.child,
super.key,
});
static MeteringScreenLayoutConfig of(BuildContext context, {bool listen = true}) {
if (listen) {
return context.dependOnInheritedWidgetOfExactType<MeteringScreenLayout>()!.config;
} else {
return context.findAncestorWidgetOfExactType<MeteringScreenLayout>()!.config;
}
}
static bool featureStatusOf(BuildContext context, MeteringScreenLayoutFeature feature) {
return InheritedModel.inheritFrom<MeteringScreenLayout>(context, aspect: feature)!
.config[feature]!;
}
@override
bool updateShouldNotify(MeteringScreenLayout oldWidget) => true;
@override
bool updateShouldNotifyDependent(
MeteringScreenLayout oldWidget,
Set<MeteringScreenLayoutFeature> dependencies,
) {
for (final dependecy in dependencies) {
if (oldWidget.config[dependecy] != config[dependecy]) {
return true;
}
}
return false;
}
}

View file

@ -56,14 +56,14 @@ class CameraContainer extends StatelessWidget {
topBarOverflow += Dimens.readingContainerSingleValueHeight; topBarOverflow += Dimens.readingContainerSingleValueHeight;
topBarOverflow += Dimens.paddingS; topBarOverflow += Dimens.paddingS;
} }
if (MeteringScreenLayout.featureStatusOf( if (MeteringScreenLayout.featureOf(
context, context,
MeteringScreenLayoutFeature.extremeExposurePairs, MeteringScreenLayoutFeature.extremeExposurePairs,
)) { )) {
topBarOverflow += Dimens.readingContainerDoubleValueHeight; topBarOverflow += Dimens.readingContainerDoubleValueHeight;
topBarOverflow += Dimens.paddingS; topBarOverflow += Dimens.paddingS;
} }
if (MeteringScreenLayout.featureStatusOf( if (MeteringScreenLayout.featureOf(
context, context,
MeteringScreenLayoutFeature.filmPicker, MeteringScreenLayoutFeature.filmPicker,
)) { )) {

View file

@ -42,7 +42,7 @@ class ReadingsContainer extends StatelessWidget {
const _EquipmentProfilePicker(), const _EquipmentProfilePicker(),
const _InnerPadding(), const _InnerPadding(),
], ],
if (MeteringScreenLayout.featureStatusOf( if (MeteringScreenLayout.featureOf(
context, context,
MeteringScreenLayoutFeature.extremeExposurePairs, MeteringScreenLayoutFeature.extremeExposurePairs,
)) ...[ )) ...[
@ -60,7 +60,7 @@ class ReadingsContainer extends StatelessWidget {
), ),
const _InnerPadding(), const _InnerPadding(),
], ],
if (MeteringScreenLayout.featureStatusOf( if (MeteringScreenLayout.featureOf(
context, context,
MeteringScreenLayoutFeature.filmPicker, MeteringScreenLayoutFeature.filmPicker,
)) ...[ )) ...[

View file

@ -7,7 +7,6 @@ import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
import 'package:lightmeter/environment.dart'; import 'package:lightmeter/environment.dart';
import 'package:lightmeter/providers/equipment_profile_provider.dart'; import 'package:lightmeter/providers/equipment_profile_provider.dart';
import 'package:lightmeter/providers/ev_source_type_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/bloc_metering.dart';
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.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/camera_container/provider_container_camera.dart';
@ -31,17 +30,11 @@ class _MeteringScreenState extends State<MeteringScreen> {
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
_bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context))); _bloc.add(EquipmentProfileChangedEvent(EquipmentProfile.of(context)));
if (!MeteringScreenLayout.featureStatusOf(context, MeteringScreenLayoutFeature.filmPicker)) {
_bloc.add(const FilmChangedEvent(Film.other()));
}
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InheritedWidgetListener<StopType>( return _InheritedListeners(
onDidChangeDependencies: (value) {
context.read<MeteringBloc>().add(StopTypeChangedEvent(value));
},
child: Scaffold( child: Scaffold(
backgroundColor: Theme.of(context).colorScheme.background, backgroundColor: Theme.of(context).colorScheme.background,
body: Column( body: Column(
@ -64,8 +57,8 @@ class _MeteringScreenState extends State<MeteringScreen> {
BlocBuilder<MeteringBloc, MeteringState>( BlocBuilder<MeteringBloc, MeteringState>(
builder: (context, state) => MeteringBottomControlsProvider( builder: (context, state) => MeteringBottomControlsProvider(
ev: state is MeteringDataState ? state.ev : null, ev: state is MeteringDataState ? state.ev : null,
isMetering: isMetering: state is LoadingState ||
state is LoadingState || state is MeteringDataState && state.continuousMetering, state is MeteringDataState && state.continuousMetering,
hasError: state is MeteringDataState && state.hasError, hasError: state is MeteringDataState && state.hasError,
onSwitchEvSourceType: context.read<Environment>().hasLightSensor onSwitchEvSourceType: context.read<Environment>().hasLightSensor
? EvSourceTypeProvider.of(context).toggleType ? EvSourceTypeProvider.of(context).toggleType
@ -81,6 +74,28 @@ class _MeteringScreenState extends State<MeteringScreen> {
} }
} }
class _InheritedListeners extends StatelessWidget {
final Widget child;
const _InheritedListeners({required this.child});
@override
Widget build(BuildContext context) {
return InheritedWidgetListener<StopType>(
onDidChangeDependencies: (value) {
context.read<MeteringBloc>().add(StopTypeChangedEvent(value));
},
child: InheritedModelAspectListener<MeteringScreenLayoutFeature, bool>(
aspect: MeteringScreenLayoutFeature.filmPicker,
onDidChangeDependencies: (value) {
if (!value) context.read<MeteringBloc>().add(const FilmChangedEvent(Film.other()));
},
child: child,
),
);
}
}
class _MeteringContainerBuidler extends StatelessWidget { class _MeteringContainerBuidler extends StatelessWidget {
final ExposurePair? fastest; final ExposurePair? fastest;
final ExposurePair? slowest; final ExposurePair? slowest;

View file

@ -62,45 +62,6 @@ class InheritedWidgetBase<T> extends InheritedWidget {
bool updateShouldNotify(InheritedWidgetBase<T> oldWidget) => true; bool updateShouldNotify(InheritedWidgetBase<T> oldWidget) => true;
} }
// class InheritedModelBase<A, R> extends InheritedModel<A> {
// final Map<A, R> data;
// const InheritedModelBase({
// required this.data,
// required super.child,
// super.key,
// });
// static Map<A, R> of<R, A>(BuildContext context, {bool listen = true}) {
// if (listen) {
// return context.dependOnInheritedWidgetOfExactType<InheritedModelBase<A, R>>()!.data;
// } else {
// return context.findAncestorWidgetOfExactType<InheritedModelBase<A, R>>()!.data;
// }
// }
// static R featureStatusOf<A, R>(BuildContext context, A aspect) {
// return InheritedModel.inheritFrom<InheritedModelBase<A, R>>(context, aspect: aspect)!
// .data[aspect]!;
// }
// @override
// bool updateShouldNotify(InheritedModelBase oldWidget) => true;
// @override
// bool updateShouldNotifyDependent(
// InheritedModelBase<A, R> oldWidget,
// Set<A> dependencies,
// ) {
// for (final dependecy in dependencies) {
// if (oldWidget.data[dependecy] != data[dependecy]) {
// return true;
// }
// }
// return false;
// }
// }
extension InheritedWidgetBaseContext on BuildContext { extension InheritedWidgetBaseContext on BuildContext {
T get<T>() { T get<T>() {
return InheritedWidgetBase.of<T>(this, listen: false); return InheritedWidgetBase.of<T>(this, listen: false);
@ -110,3 +71,101 @@ extension InheritedWidgetBaseContext on BuildContext {
return InheritedWidgetBase.of<T>(this); return InheritedWidgetBase.of<T>(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<A extends Object, T> extends StatefulWidget {
final A aspect;
final ValueChanged<T> onDidChangeDependencies;
final Widget child;
const InheritedModelAspectListener({
required this.aspect,
required this.onDidChangeDependencies,
required this.child,
super.key,
});
@override
State<InheritedModelAspectListener<A, T>> createState() =>
_InheritedModelAspectListenerState<A, T>();
}
class _InheritedModelAspectListenerState<A extends Object, T>
extends State<InheritedModelAspectListener<A, T>> {
@override
void didChangeDependencies() {
super.didChangeDependencies();
widget.onDidChangeDependencies(context.listenModelFeature<A, T>(widget.aspect));
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
class InheritedModelBase<A, T> extends InheritedModel<A> {
final Map<A, T> data;
const InheritedModelBase({
required this.data,
required super.child,
super.key,
});
static Map<A, T> of<A, T>(BuildContext context, {bool listen = true}) {
if (listen) {
return context.dependOnInheritedWidgetOfExactType<InheritedModelBase<A, T>>()!.data;
} else {
return context.findAncestorWidgetOfExactType<InheritedModelBase<A, T>>()!.data;
}
}
static T featureOf<A extends Object, T>(BuildContext context, A aspect) {
return InheritedModel.inheritFrom<InheritedModelBase<A, T>>(context, aspect: aspect)!
.data[aspect]!;
}
@override
bool updateShouldNotify(InheritedModelBase oldWidget) => true;
@override
bool updateShouldNotifyDependent(
InheritedModelBase<A, T> oldWidget,
Set<A> dependencies,
) {
for (final dependecy in dependencies) {
if (oldWidget.data[dependecy] != data[dependecy]) {
return true;
}
}
return false;
}
}
extension InheritedModelBaseContext on BuildContext {
Map<A, T> getModel<A, T>() {
return InheritedModelBase.of<A, T>(this, listen: false);
}
Map<A, T> listenModel<A, T>() {
return InheritedModelBase.of<A, T>(this);
}
T listenModelFeature<A extends Object, T>(A aspect) {
return InheritedModelBase.featureOf<A, T>(this, aspect);
}
}