mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-25 17:00:39 +00:00
Compare commits
5 commits
5aa6dccd3c
...
61620acf97
Author | SHA1 | Date | |
---|---|---|---|
|
61620acf97 | ||
|
2486c32213 | ||
|
a4e7f9d3f9 | ||
|
2fe36e0d9e | ||
|
049d0c7690 |
7 changed files with 445 additions and 58 deletions
|
@ -1,5 +1,8 @@
|
||||||
<img src="resources/social_preview.png" width="100%" />
|
<img src="resources/social_preview.png" width="100%" />
|
||||||
|
|
||||||
|
![](https://github.com/vodemn/m3_lightmeter/actions/workflows/pr_check.yml/badge.svg)
|
||||||
|
![](https://github.com/vodemn/m3_lightmeter/actions/workflows/create_release.yml/badge.svg)
|
||||||
|
|
||||||
# Table of contents
|
# Table of contents
|
||||||
|
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
|
|
|
@ -30,20 +30,23 @@ class ApplicationWrapper extends StatelessWidget {
|
||||||
builder: (_, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (snapshot.data != null) {
|
if (snapshot.data != null) {
|
||||||
final iapService = IAPStorageService(snapshot.data![0] as SharedPreferences);
|
final iapService = IAPStorageService(snapshot.data![0] as SharedPreferences);
|
||||||
|
final userPreferencesService = UserPreferencesService(snapshot.data![0] as SharedPreferences);
|
||||||
|
final hasLightSensor = snapshot.data![1] as bool;
|
||||||
return ServicesProvider(
|
return ServicesProvider(
|
||||||
caffeineService: const CaffeineService(),
|
caffeineService: const CaffeineService(),
|
||||||
environment: env.copyWith(hasLightSensor: snapshot.data![1] as bool),
|
environment: env.copyWith(hasLightSensor: hasLightSensor),
|
||||||
hapticsService: const HapticsService(),
|
hapticsService: const HapticsService(),
|
||||||
lightSensorService: const LightSensorService(LocalPlatform()),
|
lightSensorService: const LightSensorService(LocalPlatform()),
|
||||||
permissionsService: const PermissionsService(),
|
permissionsService: const PermissionsService(),
|
||||||
userPreferencesService:
|
userPreferencesService: userPreferencesService,
|
||||||
UserPreferencesService(snapshot.data![0] as SharedPreferences),
|
|
||||||
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
volumeEventsService: const VolumeEventsService(LocalPlatform()),
|
||||||
child: EquipmentProfileProvider(
|
child: EquipmentProfileProvider(
|
||||||
storageService: iapService,
|
storageService: iapService,
|
||||||
child: FilmsProvider(
|
child: FilmsProvider(
|
||||||
storageService: iapService,
|
storageService: iapService,
|
||||||
child: UserPreferencesProvider(
|
child: UserPreferencesProvider(
|
||||||
|
hasLightSensor: hasLightSensor,
|
||||||
|
userPreferencesService: userPreferencesService,
|
||||||
child: child,
|
child: child,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
import 'package:lightmeter/data/volume_events_service.dart';
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
|
|
||||||
|
// coverage:ignore-start
|
||||||
class ServicesProvider extends InheritedWidget {
|
class ServicesProvider extends InheritedWidget {
|
||||||
final CaffeineService caffeineService;
|
final CaffeineService caffeineService;
|
||||||
final Environment environment;
|
final Environment environment;
|
||||||
|
@ -34,3 +35,4 @@ class ServicesProvider extends InheritedWidget {
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotify(ServicesProvider oldWidget) => false;
|
bool updateShouldNotify(ServicesProvider oldWidget) => false;
|
||||||
}
|
}
|
||||||
|
// coverage:ignore-end
|
||||||
|
|
|
@ -8,14 +8,20 @@ import 'package:lightmeter/data/models/supported_locale.dart';
|
||||||
import 'package:lightmeter/data/models/theme_type.dart';
|
import 'package:lightmeter/data/models/theme_type.dart';
|
||||||
import 'package:lightmeter/data/shared_prefs_service.dart';
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
import 'package:lightmeter/providers/services_provider.dart';
|
|
||||||
import 'package:lightmeter/res/theme.dart';
|
import 'package:lightmeter/res/theme.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class UserPreferencesProvider extends StatefulWidget {
|
class UserPreferencesProvider extends StatefulWidget {
|
||||||
|
final bool hasLightSensor;
|
||||||
|
final UserPreferencesService userPreferencesService;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const UserPreferencesProvider({required this.child, super.key});
|
const UserPreferencesProvider({
|
||||||
|
required this.hasLightSensor,
|
||||||
|
required this.userPreferencesService,
|
||||||
|
required this.child,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
static _UserPreferencesProviderState of(BuildContext context) {
|
static _UserPreferencesProviderState of(BuildContext context) {
|
||||||
return context.findAncestorStateOfType<_UserPreferencesProviderState>()!;
|
return context.findAncestorStateOfType<_UserPreferencesProviderState>()!;
|
||||||
|
@ -38,8 +44,7 @@ class UserPreferencesProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool meteringScreenFeatureOf(BuildContext context, MeteringScreenLayoutFeature feature) {
|
static bool meteringScreenFeatureOf(BuildContext context, MeteringScreenLayoutFeature feature) {
|
||||||
return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)!
|
return InheritedModel.inheritFrom<_MeteringScreenLayoutModel>(context, aspect: feature)!.data[feature]!;
|
||||||
.data[feature]!;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static StopType stopTypeOf(BuildContext context) {
|
static StopType stopTypeOf(BuildContext context) {
|
||||||
|
@ -65,28 +70,20 @@ class UserPreferencesProvider extends StatefulWidget {
|
||||||
State<UserPreferencesProvider> createState() => _UserPreferencesProviderState();
|
State<UserPreferencesProvider> createState() => _UserPreferencesProviderState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UserPreferencesProviderState extends State<UserPreferencesProvider>
|
class _UserPreferencesProviderState extends State<UserPreferencesProvider> with WidgetsBindingObserver {
|
||||||
with WidgetsBindingObserver {
|
late EvSourceType _evSourceType;
|
||||||
UserPreferencesService get userPreferencesService =>
|
late StopType _stopType = widget.userPreferencesService.stopType;
|
||||||
ServicesProvider.of(context).userPreferencesService;
|
late MeteringScreenLayoutConfig _meteringScreenLayout = widget.userPreferencesService.meteringScreenLayout;
|
||||||
|
late SupportedLocale _locale = widget.userPreferencesService.locale;
|
||||||
late bool dynamicColor = userPreferencesService.dynamicColor;
|
late ThemeType _themeType = widget.userPreferencesService.themeType;
|
||||||
late EvSourceType evSourceType;
|
late Color _primaryColor = widget.userPreferencesService.primaryColor;
|
||||||
late MeteringScreenLayoutConfig meteringScreenLayout =
|
late bool _dynamicColor = widget.userPreferencesService.dynamicColor;
|
||||||
userPreferencesService.meteringScreenLayout;
|
|
||||||
late Color primaryColor = userPreferencesService.primaryColor;
|
|
||||||
late StopType stopType = userPreferencesService.stopType;
|
|
||||||
late SupportedLocale locale = userPreferencesService.locale;
|
|
||||||
late ThemeType themeType = userPreferencesService.themeType;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
evSourceType = userPreferencesService.evSourceType;
|
_evSourceType = widget.userPreferencesService.evSourceType;
|
||||||
evSourceType = evSourceType == EvSourceType.sensor &&
|
_evSourceType = _evSourceType == EvSourceType.sensor && !widget.hasLightSensor ? EvSourceType.camera : _evSourceType;
|
||||||
!ServicesProvider.of(context).environment.hasLightSensor
|
|
||||||
? EvSourceType.camera
|
|
||||||
: evSourceType;
|
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,9 +106,8 @@ class _UserPreferencesProviderState extends State<UserPreferencesProvider>
|
||||||
late final DynamicColorState state;
|
late final DynamicColorState state;
|
||||||
late final Color? dynamicPrimaryColor;
|
late final Color? dynamicPrimaryColor;
|
||||||
if (lightDynamic != null && darkDynamic != null) {
|
if (lightDynamic != null && darkDynamic != null) {
|
||||||
if (dynamicColor) {
|
if (_dynamicColor) {
|
||||||
dynamicPrimaryColor =
|
dynamicPrimaryColor = (_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
|
||||||
(_themeBrightness == Brightness.light ? lightDynamic : darkDynamic).primary;
|
|
||||||
state = DynamicColorState.enabled;
|
state = DynamicColorState.enabled;
|
||||||
} else {
|
} else {
|
||||||
dynamicPrimaryColor = null;
|
dynamicPrimaryColor = null;
|
||||||
|
@ -124,13 +120,13 @@ class _UserPreferencesProviderState extends State<UserPreferencesProvider>
|
||||||
return _UserPreferencesModel(
|
return _UserPreferencesModel(
|
||||||
brightness: _themeBrightness,
|
brightness: _themeBrightness,
|
||||||
dynamicColorState: state,
|
dynamicColorState: state,
|
||||||
evSourceType: evSourceType,
|
evSourceType: _evSourceType,
|
||||||
locale: locale,
|
locale: _locale,
|
||||||
primaryColor: dynamicPrimaryColor ?? primaryColor,
|
primaryColor: dynamicPrimaryColor ?? _primaryColor,
|
||||||
stopType: stopType,
|
stopType: _stopType,
|
||||||
themeType: themeType,
|
themeType: _themeType,
|
||||||
child: _MeteringScreenLayoutModel(
|
child: _MeteringScreenLayoutModel(
|
||||||
data: meteringScreenLayout,
|
data: _meteringScreenLayout,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -140,65 +136,65 @@ class _UserPreferencesProviderState extends State<UserPreferencesProvider>
|
||||||
|
|
||||||
void enableDynamicColor(bool enable) {
|
void enableDynamicColor(bool enable) {
|
||||||
setState(() {
|
setState(() {
|
||||||
dynamicColor = enable;
|
_dynamicColor = enable;
|
||||||
});
|
});
|
||||||
userPreferencesService.dynamicColor = enable;
|
widget.userPreferencesService.dynamicColor = enable;
|
||||||
}
|
}
|
||||||
|
|
||||||
void toggleEvSourceType() {
|
void toggleEvSourceType() {
|
||||||
if (!ServicesProvider.of(context).environment.hasLightSensor) {
|
if (!widget.hasLightSensor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
setState(() {
|
||||||
switch (evSourceType) {
|
switch (_evSourceType) {
|
||||||
case EvSourceType.camera:
|
case EvSourceType.camera:
|
||||||
evSourceType = EvSourceType.sensor;
|
_evSourceType = EvSourceType.sensor;
|
||||||
case EvSourceType.sensor:
|
case EvSourceType.sensor:
|
||||||
evSourceType = EvSourceType.camera;
|
_evSourceType = EvSourceType.camera;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
userPreferencesService.evSourceType = evSourceType;
|
widget.userPreferencesService.evSourceType = _evSourceType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setLocale(SupportedLocale locale) {
|
void setLocale(SupportedLocale locale) {
|
||||||
S.load(Locale(locale.intlName)).then((value) {
|
S.load(Locale(locale.intlName)).then((value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.locale = locale;
|
_locale = locale;
|
||||||
});
|
});
|
||||||
userPreferencesService.locale = locale;
|
widget.userPreferencesService.locale = locale;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMeteringScreenLayout(MeteringScreenLayoutConfig config) {
|
void setMeteringScreenLayout(MeteringScreenLayoutConfig config) {
|
||||||
setState(() {
|
setState(() {
|
||||||
meteringScreenLayout = config;
|
_meteringScreenLayout = config;
|
||||||
});
|
});
|
||||||
userPreferencesService.meteringScreenLayout = meteringScreenLayout;
|
widget.userPreferencesService.meteringScreenLayout = _meteringScreenLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setPrimaryColor(Color primaryColor) {
|
void setPrimaryColor(Color primaryColor) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.primaryColor = primaryColor;
|
_primaryColor = primaryColor;
|
||||||
});
|
});
|
||||||
userPreferencesService.primaryColor = primaryColor;
|
widget.userPreferencesService.primaryColor = primaryColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStopType(StopType stopType) {
|
void setStopType(StopType stopType) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.stopType = stopType;
|
_stopType = stopType;
|
||||||
});
|
});
|
||||||
userPreferencesService.stopType = stopType;
|
widget.userPreferencesService.stopType = stopType;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setThemeType(ThemeType themeType) {
|
void setThemeType(ThemeType themeType) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.themeType = themeType;
|
_themeType = themeType;
|
||||||
});
|
});
|
||||||
userPreferencesService.themeType = themeType;
|
widget.userPreferencesService.themeType = themeType;
|
||||||
}
|
}
|
||||||
|
|
||||||
Brightness get _themeBrightness {
|
Brightness get _themeBrightness {
|
||||||
switch (themeType) {
|
switch (_themeType) {
|
||||||
case ThemeType.light:
|
case ThemeType.light:
|
||||||
return Brightness.light;
|
return Brightness.light;
|
||||||
case ThemeType.dark:
|
case ThemeType.dark:
|
||||||
|
@ -258,8 +254,7 @@ class _UserPreferencesModel extends InheritedModel<_Aspect> {
|
||||||
_UserPreferencesModel oldWidget,
|
_UserPreferencesModel oldWidget,
|
||||||
Set<_Aspect> dependencies,
|
Set<_Aspect> dependencies,
|
||||||
) {
|
) {
|
||||||
return (dependencies.contains(_Aspect.dynamicColorState) &&
|
return (dependencies.contains(_Aspect.dynamicColorState) && dynamicColorState != oldWidget.dynamicColorState) ||
|
||||||
dynamicColorState != oldWidget.dynamicColorState) ||
|
|
||||||
(dependencies.contains(_Aspect.evSourceType) && evSourceType != oldWidget.evSourceType) ||
|
(dependencies.contains(_Aspect.evSourceType) && evSourceType != oldWidget.evSourceType) ||
|
||||||
(dependencies.contains(_Aspect.locale) && locale != oldWidget.locale) ||
|
(dependencies.contains(_Aspect.locale) && locale != oldWidget.locale) ||
|
||||||
(dependencies.contains(_Aspect.stopType) && stopType != oldWidget.stopType) ||
|
(dependencies.contains(_Aspect.stopType) && stopType != oldWidget.stopType) ||
|
||||||
|
|
|
@ -17,10 +17,7 @@ class SelectableInheritedModel<T> extends InheritedModel<SelectableAspect> {
|
||||||
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
|
bool updateShouldNotify(SelectableInheritedModel oldWidget) => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool updateShouldNotifyDependent(
|
bool updateShouldNotifyDependent(SelectableInheritedModel oldWidget, Set<SelectableAspect> dependencies) {
|
||||||
SelectableInheritedModel oldWidget,
|
|
||||||
Set<SelectableAspect> dependencies,
|
|
||||||
) {
|
|
||||||
if (dependencies.contains(SelectableAspect.list)) {
|
if (dependencies.contains(SelectableAspect.list)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (dependencies.contains(SelectableAspect.selected)) {
|
} else if (dependencies.contains(SelectableAspect.selected)) {
|
||||||
|
|
|
@ -110,6 +110,9 @@ void main() {
|
||||||
expectFilmsCount(mockFilms.length + 1);
|
expectFilmsCount(mockFilms.length + 1);
|
||||||
expectFilmsInUseCount(mockFilms.length + 1);
|
expectFilmsInUseCount(mockFilms.length + 1);
|
||||||
expectSelectedFilmName('');
|
expectSelectedFilmName('');
|
||||||
|
|
||||||
|
verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(0).toList()).called(1);
|
||||||
|
verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -131,6 +134,9 @@ void main() {
|
||||||
expectFilmsCount(mockFilms.length + 1);
|
expectFilmsCount(mockFilms.length + 1);
|
||||||
expectFilmsInUseCount(mockFilms.length + 1);
|
expectFilmsInUseCount(mockFilms.length + 1);
|
||||||
expectSelectedFilmName(mockFilms.first.name);
|
expectSelectedFilmName(mockFilms.first.name);
|
||||||
|
|
||||||
|
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
|
||||||
|
verify(() => mockIAPStorageService.selectedFilm = mockFilms.first).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -148,6 +154,7 @@ void main() {
|
||||||
expectFilmsInUseCount(1);
|
expectFilmsInUseCount(1);
|
||||||
expectSelectedFilmName('');
|
expectSelectedFilmName('');
|
||||||
|
|
||||||
|
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
|
||||||
verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1);
|
verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -163,6 +170,7 @@ void main() {
|
||||||
expectFilmsInUseCount(1);
|
expectFilmsInUseCount(1);
|
||||||
expectSelectedFilmName('');
|
expectSelectedFilmName('');
|
||||||
|
|
||||||
|
verifyNever(() => mockIAPStorageService.filmsInUse = any<List<Film>>());
|
||||||
verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other());
|
verifyNever(() => mockIAPStorageService.selectedFilm = const Film.other());
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -187,6 +195,9 @@ void main() {
|
||||||
expectFilmsCount(mockFilms.length + 1);
|
expectFilmsCount(mockFilms.length + 1);
|
||||||
expectFilmsInUseCount((mockFilms.length - 1) + 1);
|
expectFilmsInUseCount((mockFilms.length - 1) + 1);
|
||||||
expectSelectedFilmName('');
|
expectSelectedFilmName('');
|
||||||
|
|
||||||
|
verify(() => mockIAPStorageService.filmsInUse = mockFilms.skip(1).toList()).called(1);
|
||||||
|
verify(() => mockIAPStorageService.selectedFilm = const Film.other()).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
376
test/providers/user_preferences_provider_test.dart
Normal file
376
test/providers/user_preferences_provider_test.dart
Normal file
|
@ -0,0 +1,376 @@
|
||||||
|
import 'package:dynamic_color/test_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.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/providers/user_preferences_provider.dart';
|
||||||
|
import 'package:lightmeter/res/theme.dart';
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
import 'package:material_color_utilities/material_color_utilities.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
late _MockUserPreferencesService mockUserPreferencesService;
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
mockUserPreferencesService = _MockUserPreferencesService();
|
||||||
|
});
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
|
||||||
|
when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third);
|
||||||
|
when(() => mockUserPreferencesService.meteringScreenLayout).thenReturn({
|
||||||
|
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
||||||
|
MeteringScreenLayoutFeature.filmPicker: true,
|
||||||
|
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||||
|
MeteringScreenLayoutFeature.histogram: true,
|
||||||
|
});
|
||||||
|
when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en);
|
||||||
|
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
||||||
|
when(() => mockUserPreferencesService.primaryColor).thenReturn(primaryColorsList[5]);
|
||||||
|
when(() => mockUserPreferencesService.dynamicColor).thenReturn(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() {
|
||||||
|
reset(mockUserPreferencesService);
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> pumpTestWidget(
|
||||||
|
WidgetTester tester, {
|
||||||
|
bool hasLightSensor = true,
|
||||||
|
required WidgetBuilder builder,
|
||||||
|
}) async {
|
||||||
|
await tester.pumpWidget(
|
||||||
|
UserPreferencesProvider(
|
||||||
|
hasLightSensor: hasLightSensor,
|
||||||
|
userPreferencesService: mockUserPreferencesService,
|
||||||
|
child: _Application(builder: builder),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
group('[evSourceType]', () {
|
||||||
|
Future<void> pumpEvTestApplication(WidgetTester tester, {required bool hasLightSensor}) async {
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
hasLightSensor: hasLightSensor,
|
||||||
|
builder: (context) => Column(
|
||||||
|
children: [
|
||||||
|
Text('EV source type: ${UserPreferencesProvider.evSourceTypeOf(context)}'),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: UserPreferencesProvider.of(context).toggleEvSourceType,
|
||||||
|
child: const Text('toggleEvSourceType'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void expectEvSource(EvSourceType evSourceType) {
|
||||||
|
expect(find.text("EV source type: $evSourceType"), findsOneWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Init evSourceType when has sensor & stored sensor',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor);
|
||||||
|
await pumpEvTestApplication(tester, hasLightSensor: true);
|
||||||
|
expectEvSource(EvSourceType.sensor);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Init evSourceType when has no sensor & stored camera',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
|
||||||
|
await pumpEvTestApplication(tester, hasLightSensor: false);
|
||||||
|
expectEvSource(EvSourceType.camera);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Init evSourceType when has no sensor & stored sensor -> Reset to camera',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.sensor);
|
||||||
|
await pumpEvTestApplication(tester, hasLightSensor: false);
|
||||||
|
expectEvSource(EvSourceType.camera);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Try toggleEvSourceType() when has no sensor',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
|
||||||
|
await pumpEvTestApplication(tester, hasLightSensor: false);
|
||||||
|
await tester.tap(find.text('toggleEvSourceType'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
verifyNever(() => mockUserPreferencesService.evSourceType = EvSourceType.sensor);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Try toggleEvSourceType() when has sensor',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.evSourceType).thenReturn(EvSourceType.camera);
|
||||||
|
await pumpEvTestApplication(tester, hasLightSensor: true);
|
||||||
|
|
||||||
|
await tester.tap(find.text('toggleEvSourceType'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expectEvSource(EvSourceType.sensor);
|
||||||
|
verify(() => mockUserPreferencesService.evSourceType = EvSourceType.sensor).called(1);
|
||||||
|
|
||||||
|
await tester.tap(find.text('toggleEvSourceType'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expectEvSource(EvSourceType.camera);
|
||||||
|
verify(() => mockUserPreferencesService.evSourceType = EvSourceType.camera).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Set different stop type',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.stopType).thenReturn(StopType.third);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => Column(
|
||||||
|
children: [
|
||||||
|
Text('Stop type: ${UserPreferencesProvider.stopTypeOf(context)}'),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setStopType(StopType.full),
|
||||||
|
child: const Text('setStopType'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.text("Stop type: ${StopType.third}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text('setStopType'));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("Stop type: ${StopType.full}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.stopType = StopType.full).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Set metering screen layout config',
|
||||||
|
(tester) async {
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) {
|
||||||
|
final config = UserPreferencesProvider.meteringScreenConfigOf(context);
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
...List.generate(
|
||||||
|
config.length,
|
||||||
|
(index) => Text('${config.keys.toList()[index]}: ${config.values.toList()[index]}'),
|
||||||
|
),
|
||||||
|
...List.generate(
|
||||||
|
MeteringScreenLayoutFeature.values.length,
|
||||||
|
(index) => Text(
|
||||||
|
'${MeteringScreenLayoutFeature.values[index]}: ${UserPreferencesProvider.meteringScreenFeatureOf(context, MeteringScreenLayoutFeature.values[index])}',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setMeteringScreenLayout({
|
||||||
|
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||||
|
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||||
|
MeteringScreenLayoutFeature.filmPicker: false,
|
||||||
|
MeteringScreenLayoutFeature.histogram: true,
|
||||||
|
}),
|
||||||
|
child: const Text(''),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Match `findsNWidgets(2)` to verify that `meteringScreenFeatureOf` specific results are the same as the whole config
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: true"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: true"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2));
|
||||||
|
|
||||||
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: false"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: false"), findsNWidgets(2));
|
||||||
|
expect(find.text("${MeteringScreenLayoutFeature.histogram}: true"), findsNWidgets(2));
|
||||||
|
verify(
|
||||||
|
() => mockUserPreferencesService.meteringScreenLayout = {
|
||||||
|
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
||||||
|
MeteringScreenLayoutFeature.filmPicker: false,
|
||||||
|
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
||||||
|
MeteringScreenLayoutFeature.histogram: true,
|
||||||
|
},
|
||||||
|
).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Set different locale',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setLocale(SupportedLocale.fr),
|
||||||
|
child: Text('${UserPreferencesProvider.localeOf(context)}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.text("${SupportedLocale.en}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text("${SupportedLocale.en}"));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${SupportedLocale.fr}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.locale = SupportedLocale.fr).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('[theme]', () {
|
||||||
|
testWidgets(
|
||||||
|
'Set dark theme type',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => Column(
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setThemeType(ThemeType.dark),
|
||||||
|
child: Text('${UserPreferencesProvider.themeTypeOf(context)}'),
|
||||||
|
),
|
||||||
|
Text('${Theme.of(context).colorScheme.brightness}')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.text("${ThemeType.light}"), findsOneWidget);
|
||||||
|
expect(find.text("${Brightness.light}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text("${ThemeType.light}"));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${ThemeType.dark}"), findsOneWidget);
|
||||||
|
expect(find.text("${Brightness.dark}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.themeType = ThemeType.dark).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Set systemDefault theme type and toggle platform brightness',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => Column(
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setThemeType(ThemeType.systemDefault),
|
||||||
|
child: Text('${UserPreferencesProvider.themeTypeOf(context)}'),
|
||||||
|
),
|
||||||
|
Text('${Theme.of(context).colorScheme.brightness}')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
TestWidgetsFlutterBinding.instance.platformDispatcher.platformBrightnessTestValue = Brightness.dark;
|
||||||
|
expect(find.text("${ThemeType.light}"), findsOneWidget);
|
||||||
|
expect(find.text("${Brightness.light}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text("${ThemeType.light}"));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${ThemeType.systemDefault}"), findsOneWidget);
|
||||||
|
expect(find.text("${Brightness.dark}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.themeType = ThemeType.systemDefault).called(1);
|
||||||
|
|
||||||
|
TestWidgetsFlutterBinding.instance.platformDispatcher.platformBrightnessTestValue = Brightness.light;
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${ThemeType.systemDefault}"), findsOneWidget);
|
||||||
|
expect(find.text("${Brightness.light}"), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Set primary color',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.primaryColor).thenReturn(primaryColorsList[5]);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).setPrimaryColor(primaryColorsList[7]),
|
||||||
|
child: Text('${UserPreferencesProvider.themeOf(context).primaryColor}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(find.text("${primaryColorsList[5]}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text("${primaryColorsList[5]}"));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${primaryColorsList[7]}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.primaryColor = primaryColorsList[7]).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Dynamic colors not available',
|
||||||
|
(tester) async {
|
||||||
|
when(() => mockUserPreferencesService.dynamicColor).thenReturn(true);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).enableDynamicColor(false),
|
||||||
|
child: Text('${UserPreferencesProvider.dynamicColorStateOf(context)}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(
|
||||||
|
find.text("${DynamicColorState.unavailable}"),
|
||||||
|
findsOneWidget,
|
||||||
|
reason:
|
||||||
|
"Even though dynamic colors usage is enabled, the core palette can be unavailable. Therefore `DynamicColorState` is also unavailable.",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'Toggle dynamic color state',
|
||||||
|
(tester) async {
|
||||||
|
DynamicColorTestingUtils.setMockDynamicColors(corePalette: CorePalette.of(0xffffffff));
|
||||||
|
when(() => mockUserPreferencesService.dynamicColor).thenReturn(true);
|
||||||
|
await pumpTestWidget(
|
||||||
|
tester,
|
||||||
|
builder: (context) => ElevatedButton(
|
||||||
|
onPressed: () => UserPreferencesProvider.of(context).enableDynamicColor(false),
|
||||||
|
child: Text('${UserPreferencesProvider.dynamicColorStateOf(context)}'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${DynamicColorState.enabled}"), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(find.text("${DynamicColorState.enabled}"));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
expect(find.text("${DynamicColorState.disabled}"), findsOneWidget);
|
||||||
|
verify(() => mockUserPreferencesService.dynamicColor = false).called(1);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Application extends StatelessWidget {
|
||||||
|
final WidgetBuilder builder;
|
||||||
|
|
||||||
|
const _Application({required this.builder});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
theme: UserPreferencesProvider.themeOf(context),
|
||||||
|
home: Scaffold(body: Center(child: Builder(builder: builder))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue