diff --git a/android/app/build.gradle b/android/app/build.gradle index 4bc9578..53774dc 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,10 +33,6 @@ apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" -gradle.beforeProject({ project-> - project.setProperty("target-platform", "android-arm,android-arm64") -}) - android { compileSdkVersion 33 ndkVersion flutter.ndkVersion @@ -112,5 +108,5 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "com.android.billingclient:billing-ktx:5.1.0" + implementation "com.android.billingclient:billing-ktx:6.0.0" } diff --git a/android/build.gradle b/android/build.gradle index 346e639..951d253 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,8 +6,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath 'com.google.gms:google-services:4.3.10' + classpath 'com.android.tools.build:gradle:7.4.2' + classpath 'com.google.gms:google-services:4.3.15' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index cb24abd..3c472b9 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/lib/data/caffeine_service.dart b/lib/data/caffeine_service.dart index cfb0d50..da36968 100644 --- a/lib/data/caffeine_service.dart +++ b/lib/data/caffeine_service.dart @@ -9,7 +9,7 @@ class CaffeineService { return _methodChannel.invokeMethod("isKeepScreenOn").then((value) => value!); } - Future keepScreenOn(bool keep) async { - await _methodChannel.invokeMethod("setKeepScreenOn", keep); + Future keepScreenOn(bool keep) async { + return _methodChannel.invokeMethod("setKeepScreenOn", keep).then((value) => value!); } } diff --git a/lib/data/models/film.dart b/lib/data/models/film.dart index d40c5a0..2da1c9b 100644 --- a/lib/data/models/film.dart +++ b/lib/data/models/film.dart @@ -23,6 +23,8 @@ double log10polynomian( /// do not have any reciprocity failure information, as these films are ment to be used in cinema /// with appropriate light and pretty short shutter speeds. /// +/// Because of this: https://github.com/dart-lang/sdk/issues/38934#issuecomment-803938315 +/// `super` calls are ignored in test coverage class Film { final String name; final int iso; @@ -78,26 +80,26 @@ class Film { /// https://www.tate.org.uk/documents/598/page_6_7_agfa_stocks_0.pdf /// https://www.filmwasters.com/forum/index.php?topic=5298.0 // {{1,1.87},{2,3.73},{3,8.06},{4,13.93},{5,21.28},{6,23.00},{7,30.12},{8,38.05},{9,44.75},{10,50.12},{20,117},{30,202},{40,293},{50,413},{60,547},{70,694},{80,853},{90,1022},{100,1202}}; -class AgfaFilm extends Film { - final double a; - final double b; - final double c; +// class AgfaFilm extends Film { +// final double a; +// final double b; +// final double c; - const AgfaFilm.apx100() - : a = 1, - b = 5, - c = 2, - super('Agfa APX 100', 100); +// const AgfaFilm.apx100() +// : a = 1, +// b = 5, +// c = 2, +// super('Agfa APX 100', 100); // coverage:ignore-line - const AgfaFilm.apx400() - : a = 1.5, - b = 4.5, - c = 3, - super('Agfa APX 400', 400); +// const AgfaFilm.apx400() +// : a = 1.5, +// b = 4.5, +// c = 3, +// super('Agfa APX 400', 400); // coverage:ignore-line - @override - double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c); -} +// @override +// double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c); +// } class FomapanFilm extends Film { final double a; @@ -109,21 +111,21 @@ class FomapanFilm extends Film { : a = 1, b = 5, c = 2, - super('Fomapan CREATIVE 100', 100); + super('Fomapan CREATIVE 100', 100); // coverage:ignore-line /// https://www.foma.cz/en/fomapan-200 const FomapanFilm.creative200() : a = 1.5, b = 4.5, c = 3, - super('Fomapan CREATIVE 200', 200); + super('Fomapan CREATIVE 200', 200); // coverage:ignore-line /// https://www.foma.cz/en/fomapan-100 const FomapanFilm.action400() - : a = -1.25, + : a = -1.25, // coverage:ignore-line b = 5.75, c = 1.5, - super('Fomapan ACTION 400', 400); + super('Fomapan ACTION 400', 400); // coverage:ignore-line @override double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c); @@ -135,57 +137,57 @@ class IlfordFilm extends Film { /// https://www.ilfordphoto.com/amfile/file/download/file/1948/product/1650/ const IlfordFilm.ortho() : reciprocityPower = 1.25, - super('Ilford ORTHO+', 80); + super('Ilford ORTHO+', 80); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1919/product/686/ const IlfordFilm.fp4() : reciprocityPower = 1.26, - super('Ilford FP4+', 125); + super('Ilford FP4+', 125); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1903/product/691/ const IlfordFilm.hp5() : reciprocityPower = 1.31, - super('Ilford HP5+', 400); + super('Ilford HP5+', 400); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/3/product/679/ const IlfordFilm.delta100() : reciprocityPower = 1.26, - super('Ilford DELTA 100', 100); + super('Ilford DELTA 100', 100); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1915/product/684/ const IlfordFilm.delta400() : reciprocityPower = 1.41, - super('Ilford DELTA 400', 400); + super('Ilford DELTA 400', 400); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1913/product/682/ const IlfordFilm.delta3200() : reciprocityPower = 1.33, - super('Ilford DELTA 3200', 3200); + super('Ilford DELTA 3200', 3200); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1905/product/699/ const IlfordFilm.panf() : reciprocityPower = 1.33, - super('Ilford Pan F+', 50); + super('Ilford Pan F+', 50); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1907/product/701/ const IlfordFilm.sfx200() : reciprocityPower = 1.31, - super('Ilford SFX 200', 200); + super('Ilford SFX 200', 200); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1909/product/703/ const IlfordFilm.xp2super() : reciprocityPower = 1.31, - super('Ilford XP2 SUPER', 400); + super('Ilford XP2 SUPER', 400); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1958/product/696/ const IlfordFilm.pan100() : reciprocityPower = 1.26, - super('Kentemere 100', 100); + super('Kentemere 100', 100); // coverage:ignore-line /// https://www.ilfordphoto.com/amfile/file/download/file/1959/product/697/ const IlfordFilm.pan400() : reciprocityPower = 1.30, - super('Kentemere 400', 400); + super('Kentemere 400', 400); // coverage:ignore-line @override double reciprocityFormula(double t) => pow(t, reciprocityPower).toDouble(); @@ -197,34 +199,34 @@ class KodakFilm extends Film { final double c; const KodakFilm.tmax100() - : a = 1 / 6, - b = 0, - c = 4 / 3, - super('Kodak T-MAX 100', 100); + : a = 1 / 6, // coverage:ignore-line + b = 0, // coverage:ignore-line + c = 4 / 3, // coverage:ignore-line + super('Kodak T-MAX 100', 100); // coverage:ignore-line const KodakFilm.tmax400() - : a = 2 / 3, - b = -1 / 2, - c = 4 / 3, - super('Kodak T-MAX 400', 400); + : a = 2 / 3, // coverage:ignore-line + b = -1 / 2, // coverage:ignore-line + c = 4 / 3, // coverage:ignore-line + super('Kodak T-MAX 400', 400); // coverage:ignore-line const KodakFilm.tmax3200() - : a = 7 / 6, - b = -1, - c = 4 / 3, - super('Kodak T-MAX 3200', 3200); + : a = 7 / 6, // coverage:ignore-line + b = -1, // coverage:ignore-line + c = 4 / 3, // coverage:ignore-line + super('Kodak T-MAX 3200', 3200); // coverage:ignore-line const KodakFilm.trix320() : a = 2, b = 1, c = 2, - super('Kodak TRI-X 320', 320); + super('Kodak TRI-X 320', 320); // coverage:ignore-line const KodakFilm.trix400() : a = 2, b = 1, c = 2, - super('Kodak TRI-X 400', 400); + super('Kodak TRI-X 400', 400); // coverage:ignore-line @override double reciprocityFormula(double t) => t * log10polynomian(t, a, b, c); diff --git a/lib/data/models/supported_locale.dart b/lib/data/models/supported_locale.dart index d06046f..e5f6dcb 100644 --- a/lib/data/models/supported_locale.dart +++ b/lib/data/models/supported_locale.dart @@ -1,20 +1,5 @@ -import 'package:intl/intl.dart'; - enum SupportedLocale { en, fr, ru } -SupportedLocale get currentLanguage { - switch (Intl.getCurrentLocale()) { - case "en": - return SupportedLocale.en; - case "fr": - return SupportedLocale.fr; - case "ru": - return SupportedLocale.ru; - default: - return SupportedLocale.en; - } -} - extension SupportedLocaleExtension on SupportedLocale { String get intlName => toString().replaceAll("SupportedLocale.", ""); diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index a86a7d1..8fbb6af 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -10,30 +10,32 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; import 'package:shared_preferences/shared_preferences.dart'; class UserPreferencesService { - static const _isoKey = "iso"; - static const _ndFilterKey = "ndFilter"; + static const isoKey = "iso"; + static const ndFilterKey = "ndFilter"; - static const _evSourceTypeKey = "evSourceType"; - static const _cameraEvCalibrationKey = "cameraEvCalibration"; - static const _lightSensorEvCalibrationKey = "lightSensorEvCalibration"; - static const _meteringScreenLayoutKey = "meteringScreenLayout"; - static const _filmKey = "film"; + static const evSourceTypeKey = "evSourceType"; + static const stopTypeKey = "stopType"; + static const cameraEvCalibrationKey = "cameraEvCalibration"; + static const lightSensorEvCalibrationKey = "lightSensorEvCalibration"; + static const meteringScreenLayoutKey = "meteringScreenLayout"; + static const filmKey = "film"; - static const _caffeineKey = "caffeine"; - static const _hapticsKey = "haptics"; - static const _localeKey = "locale"; + static const caffeineKey = "caffeine"; + static const hapticsKey = "haptics"; + static const localeKey = "locale"; - static const _themeTypeKey = "themeType"; - static const _primaryColorKey = "primaryColor"; - static const _dynamicColorKey = "dynamicColor"; + static const themeTypeKey = "themeType"; + static const primaryColorKey = "primaryColor"; + static const dynamicColorKey = "dynamicColor"; final SharedPreferences _sharedPreferences; UserPreferencesService(this._sharedPreferences) { - _migrateOldKeys(); + migrateOldKeys(); } - Future _migrateOldKeys() async { + @visibleForTesting + Future migrateOldKeys() async { final legacyIsoIndex = _sharedPreferences.getInt("curIsoIndex"); if (legacyIsoIndex != null) { iso = IsoValue.values[legacyIsoIndex]; @@ -69,22 +71,25 @@ class UserPreferencesService { } IsoValue get iso => - IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(_isoKey) ?? 100)); - set iso(IsoValue value) => _sharedPreferences.setInt(_isoKey, value.value); + IsoValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(isoKey) ?? 100)); + set iso(IsoValue value) => _sharedPreferences.setInt(isoKey, value.value); NdValue get ndFilter => - NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(_ndFilterKey) ?? 0)); - set ndFilter(NdValue value) => _sharedPreferences.setInt(_ndFilterKey, value.value); + NdValue.values.firstWhere((v) => v.value == (_sharedPreferences.getInt(ndFilterKey) ?? 0)); + set ndFilter(NdValue value) => _sharedPreferences.setInt(ndFilterKey, value.value); EvSourceType get evSourceType => - EvSourceType.values[_sharedPreferences.getInt(_evSourceTypeKey) ?? 0]; - set evSourceType(EvSourceType value) => _sharedPreferences.setInt(_evSourceTypeKey, value.index); + EvSourceType.values[_sharedPreferences.getInt(evSourceTypeKey) ?? 0]; + set evSourceType(EvSourceType value) => _sharedPreferences.setInt(evSourceTypeKey, value.index); - bool get caffeine => _sharedPreferences.getBool(_caffeineKey) ?? false; - set caffeine(bool value) => _sharedPreferences.setBool(_caffeineKey, value); + bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false; + set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value); + + StopType get stopType => StopType.values[_sharedPreferences.getInt(stopTypeKey) ?? 2]; + set stopType(StopType value) => _sharedPreferences.setInt(stopTypeKey, value.index); MeteringScreenLayoutConfig get meteringScreenLayout { - final configJson = _sharedPreferences.getString(_meteringScreenLayoutKey); + final configJson = _sharedPreferences.getString(meteringScreenLayoutKey); if (configJson != null) { return MeteringScreenLayoutConfigJson.fromJson( json.decode(configJson) as Map, @@ -98,44 +103,44 @@ class UserPreferencesService { } set meteringScreenLayout(MeteringScreenLayoutConfig value) => - _sharedPreferences.setString(_meteringScreenLayoutKey, json.encode(value.toJson())); + _sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson())); - bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? true; - set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value); + bool get haptics => _sharedPreferences.getBool(hapticsKey) ?? true; + set haptics(bool value) => _sharedPreferences.setBool(hapticsKey, value); SupportedLocale get locale => SupportedLocale.values.firstWhere( - (e) => e.toString() == _sharedPreferences.getString(_localeKey), + (e) => e.toString() == _sharedPreferences.getString(localeKey), orElse: () => SupportedLocale.en, ); - set locale(SupportedLocale value) => _sharedPreferences.setString(_localeKey, value.toString()); + set locale(SupportedLocale value) => _sharedPreferences.setString(localeKey, value.toString()); - double get cameraEvCalibration => _sharedPreferences.getDouble(_cameraEvCalibrationKey) ?? 0.0; + double get cameraEvCalibration => _sharedPreferences.getDouble(cameraEvCalibrationKey) ?? 0.0; set cameraEvCalibration(double value) => - _sharedPreferences.setDouble(_cameraEvCalibrationKey, value); + _sharedPreferences.setDouble(cameraEvCalibrationKey, value); double get lightSensorEvCalibration => - _sharedPreferences.getDouble(_lightSensorEvCalibrationKey) ?? 0.0; + _sharedPreferences.getDouble(lightSensorEvCalibrationKey) ?? 0.0; set lightSensorEvCalibration(double value) => - _sharedPreferences.setDouble(_lightSensorEvCalibrationKey, value); + _sharedPreferences.setDouble(lightSensorEvCalibrationKey, value); - ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(_themeTypeKey) ?? 0]; - set themeType(ThemeType value) => _sharedPreferences.setInt(_themeTypeKey, value.index); + ThemeType get themeType => ThemeType.values[_sharedPreferences.getInt(themeTypeKey) ?? 0]; + set themeType(ThemeType value) => _sharedPreferences.setInt(themeTypeKey, value.index); - Color get primaryColor => Color(_sharedPreferences.getInt(_primaryColorKey) ?? 0xff2196f3); - set primaryColor(Color value) => _sharedPreferences.setInt(_primaryColorKey, value.value); + Color get primaryColor => Color(_sharedPreferences.getInt(primaryColorKey) ?? 0xff2196f3); + set primaryColor(Color value) => _sharedPreferences.setInt(primaryColorKey, value.value); - bool get dynamicColor => _sharedPreferences.getBool(_dynamicColorKey) ?? false; - set dynamicColor(bool value) => _sharedPreferences.setBool(_dynamicColorKey, value); + bool get dynamicColor => _sharedPreferences.getBool(dynamicColorKey) ?? false; + set dynamicColor(bool value) => _sharedPreferences.setBool(dynamicColorKey, value); Film get film => Film.values.firstWhere( - (e) => e.name == _sharedPreferences.getString(_filmKey), + (e) => e.name == _sharedPreferences.getString(filmKey), orElse: () => Film.values.first, ); - set film(Film value) => _sharedPreferences.setString(_filmKey, value.name); + set film(Film value) => _sharedPreferences.setString(filmKey, value.name); - String get selectedEquipmentProfileId => ''; - set selectedEquipmentProfileId(String id) {} + String get selectedEquipmentProfileId => ''; // coverage:ignore-line + set selectedEquipmentProfileId(String id) {} // coverage:ignore-line - List get equipmentProfiles => []; - set equipmentProfiles(List profiles) {} + List get equipmentProfiles => []; // coverage:ignore-line + set equipmentProfiles(List profiles) {} // coverage:ignore-line } diff --git a/lib/providers/stop_type_provider.dart b/lib/providers/stop_type_provider.dart index 690f65f..3b20dd5 100644 --- a/lib/providers/stop_type_provider.dart +++ b/lib/providers/stop_type_provider.dart @@ -1,4 +1,5 @@ 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'; @@ -16,14 +17,12 @@ class StopTypeProvider extends StatefulWidget { } class StopTypeProviderState extends State { - StopType _stopType = StopType.third; + late StopType _stopType; - StopType get stopType => _stopType; - - void set(StopType type) { - setState(() { - _stopType = type; - }); + @override + void initState() { + super.initState(); + _stopType = context.get().stopType; } @override @@ -33,4 +32,11 @@ class StopTypeProviderState extends State { child: widget.child, ); } + + void set(StopType type) { + setState(() { + _stopType = type; + }); + context.get().stopType = type; + } } diff --git a/test/data/caffeine_service_test.dart b/test/data/caffeine_service_test.dart new file mode 100644 index 0000000..0c80fac --- /dev/null +++ b/test/data/caffeine_service_test.dart @@ -0,0 +1,76 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/caffeine_service.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late CaffeineService service; + + const methodChannel = MethodChannel('com.vodemn.lightmeter/keepScreenOn'); + Future? methodCallSuccessHandler(MethodCall methodCall) async { + switch (methodCall.method) { + case "isKeepScreenOn": + return true; + case "setKeepScreenOn": + return methodCall.arguments as bool; + default: + return null; + } + } + + setUp(() { + service = const CaffeineService(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, methodCallSuccessHandler); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + }); + + group( + 'isKeepScreenOn()', + () { + test('true', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + switch (methodCall.method) { + case "isKeepScreenOn": + return true; + default: + return null; + } + }); + expectLater(service.isKeepScreenOn(), completion(true)); + }); + + test('false', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + switch (methodCall.method) { + case "isKeepScreenOn": + return false; + default: + return null; + } + }); + expectLater(service.isKeepScreenOn(), completion(false)); + }); + }, + ); + + group( + 'keepScreenOn()', + () { + test('true', () async => expectLater(service.keepScreenOn(true), completion(true))); + + test('false', () async => expectLater(service.keepScreenOn(false), completion(false))); + }, + ); +} diff --git a/test/data/light_sensor_service_test.dart b/test/data/light_sensor_service_test.dart new file mode 100644 index 0000000..1256e18 --- /dev/null +++ b/test/data/light_sensor_service_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/light_sensor_service.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late LightSensorService service; + + const methodChannel = MethodChannel('system_feature'); + // TODO: add event channel mock + //const eventChannel = EventChannel('light.eventChannel'); + + setUp(() { + service = const LightSensorService(); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + }); + + group( + 'hasSensor()', + () { + test('true', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + switch (methodCall.method) { + case "sensor": + return true; + default: + return null; + } + }); + expectLater(service.hasSensor(), completion(true)); + }); + + test('false', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + switch (methodCall.method) { + case "sensor": + return false; + default: + return null; + } + }); + expectLater(service.hasSensor(), completion(false)); + }); + test('null', () async { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, null); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, (methodCall) async { + switch (methodCall.method) { + case "sensor": + return null; + default: + return null; + } + }); + expectLater(service.hasSensor(), completion(false)); + }); + }, + ); +} diff --git a/test/data/models/film_test.dart b/test/data/models/film_test.dart new file mode 100644 index 0000000..2feb690 --- /dev/null +++ b/test/data/models/film_test.dart @@ -0,0 +1,121 @@ +import 'package:lightmeter/data/models/film.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:test/test.dart'; + +void main() { + test('iso', () { + expect(const Film.other().iso, 0); + expect(const FomapanFilm.creative100().iso, 100); + expect(const FomapanFilm.creative200().iso, 200); + expect(const FomapanFilm.action400().iso, 400); + expect(const IlfordFilm.ortho().iso, 80); + expect(const IlfordFilm.delta100().iso, 100); + expect(const IlfordFilm.delta400().iso, 400); + expect(const IlfordFilm.delta3200().iso, 3200); + expect(const IlfordFilm.fp4().iso, 125); + expect(const IlfordFilm.hp5().iso, 400); + expect(const IlfordFilm.panf().iso, 50); + expect(const IlfordFilm.sfx200().iso, 200); + expect(const IlfordFilm.xp2super().iso, 400); + expect(const IlfordFilm.pan100().iso, 100); + expect(const IlfordFilm.pan400().iso, 400); + expect(const KodakFilm.tmax100().iso, 100); + expect(const KodakFilm.tmax400().iso, 400); + expect(const KodakFilm.tmax3200().iso, 3200); + expect(const KodakFilm.trix320().iso, 320); + expect(const KodakFilm.trix400().iso, 400); + }); + + test('toString()', () { + expect(const Film.other().toString(), ""); + expect(const FomapanFilm.creative100().toString(), "Fomapan CREATIVE 100"); + expect(const FomapanFilm.creative200().toString(), "Fomapan CREATIVE 200"); + expect(const FomapanFilm.action400().toString(), "Fomapan ACTION 400"); + expect(const IlfordFilm.ortho().toString(), "Ilford ORTHO+"); + expect(const IlfordFilm.delta100().toString(), "Ilford DELTA 100"); + expect(const IlfordFilm.delta400().toString(), "Ilford DELTA 400"); + expect(const IlfordFilm.delta3200().toString(), "Ilford DELTA 3200"); + expect(const IlfordFilm.fp4().toString(), "Ilford FP4+"); + expect(const IlfordFilm.hp5().toString(), "Ilford HP5+"); + expect(const IlfordFilm.panf().toString(), "Ilford Pan F+"); + expect(const IlfordFilm.sfx200().toString(), "Ilford SFX 200"); + expect(const IlfordFilm.xp2super().toString(), "Ilford XP2 SUPER"); + expect(const IlfordFilm.pan100().toString(), "Kentemere 100"); + expect(const IlfordFilm.pan400().toString(), "Kentemere 400"); + expect(const KodakFilm.tmax100().toString(), "Kodak T-MAX 100"); + expect(const KodakFilm.tmax400().toString(), "Kodak T-MAX 400"); + expect(const KodakFilm.tmax3200().toString(), "Kodak T-MAX 3200"); + expect(const KodakFilm.trix320().toString(), "Kodak TRI-X 320"); + expect(const KodakFilm.trix400().toString(), "Kodak TRI-X 400"); + }); + + group( + 'reciprocityFailure', + () { + const inputSpeeds = [ + ShutterSpeedValue(1000, true, StopType.full), + ShutterSpeedValue(1, false, StopType.full), + ShutterSpeedValue(16, false, StopType.full) + ]; + test('No change `Film.other()`', () { + expect( + const Film.other().reciprocityFailure(inputSpeeds[0]), + const ShutterSpeedValue(1000, true, StopType.full), + ); + expect( + const Film.other().reciprocityFailure(inputSpeeds[1]), + const ShutterSpeedValue(1, false, StopType.full), + ); + expect( + const Film.other().reciprocityFailure(inputSpeeds[2]), + const ShutterSpeedValue(16, false, StopType.full), + ); + }); + + test('pow `IlfordFilm.delta100()`', () { + expect( + const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[0]), + const ShutterSpeedValue(1000, true, StopType.full), + ); + expect( + const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[1]), + const ShutterSpeedValue(1, false, StopType.full), + ); + expect( + const IlfordFilm.delta100().reciprocityFailure(inputSpeeds[2]), + const ShutterSpeedValue(32.899642452994128, false, StopType.full), + ); + }); + + test('log10polynomian `FomapanFilm.creative100()`', () { + expect( + const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[0]), + const ShutterSpeedValue(1000, true, StopType.full), + ); + expect( + const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[1]), + const ShutterSpeedValue(2, false, StopType.full), + ); + expect( + const FomapanFilm.creative100().reciprocityFailure(inputSpeeds[2]), + const ShutterSpeedValue(151.52807753457483, false, StopType.full), + ); + }); + + test('log10polynomian `Kodak.tmax400()`', () { + expect( + const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[0]), + const ShutterSpeedValue(1000, true, StopType.full), + ); + expect( + const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[1]), + const ShutterSpeedValue(1.3333333333333333, false, StopType.full), + ); + expect( + const KodakFilm.tmax400().reciprocityFailure(inputSpeeds[2]), + const ShutterSpeedValue(27.166026086819844, false, StopType.full), + ); + }); + }, + ); +} diff --git a/test/data/models/metering_screen_layout_config_test.dart b/test/data/models/metering_screen_layout_config_test.dart new file mode 100644 index 0000000..53922ae --- /dev/null +++ b/test/data/models/metering_screen_layout_config_test.dart @@ -0,0 +1,31 @@ +import 'package:lightmeter/data/models/metering_screen_layout_config.dart'; +import 'package:test/test.dart'; + +void main() { + test('fromJson', () { + expect( + MeteringScreenLayoutConfigJson.fromJson({'0': true, '1': true}), + { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + }, + ); + expect( + MeteringScreenLayoutConfigJson.fromJson({'0': false, '1': false}), + { + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: false, + }, + ); + }); + + test('toJson', () { + expect( + { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + }.toJson(), + {'0': true, '1': true}, + ); + }); +} diff --git a/test/data/models/supported_locale_test.dart b/test/data/models/supported_locale_test.dart new file mode 100644 index 0000000..6d92154 --- /dev/null +++ b/test/data/models/supported_locale_test.dart @@ -0,0 +1,16 @@ +import 'package:lightmeter/data/models/supported_locale.dart'; +import 'package:test/test.dart'; + +void main() { + test('intlName', () { + expect(SupportedLocale.en.intlName, 'en'); + expect(SupportedLocale.fr.intlName, 'fr'); + expect(SupportedLocale.ru.intlName, 'ru'); + }); + + test('localizedName', () { + expect(SupportedLocale.en.localizedName, 'English'); + expect(SupportedLocale.fr.localizedName, 'Français'); + expect(SupportedLocale.ru.localizedName, 'Русский'); + }); +} diff --git a/test/data/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart new file mode 100644 index 0000000..1a2d05b --- /dev/null +++ b/test/data/shared_prefs_service_test.dart @@ -0,0 +1,411 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:lightmeter/data/models/ev_source_type.dart'; +import 'package:lightmeter/data/models/film.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/theme_provider.dart'; +import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class _MockSharedPreferences extends Mock implements SharedPreferences {} + +void main() { + late _MockSharedPreferences sharedPreferences; + late UserPreferencesService service; + + setUpAll(() { + sharedPreferences = _MockSharedPreferences(); + service = UserPreferencesService(sharedPreferences); + }); + + tearDown(() { + reset(sharedPreferences); + }); + + group('migrateOldKeys()', () { + test('no legacy keys', () async { + when(() => sharedPreferences.getInt("curIsoIndex")).thenReturn(null); + when(() => sharedPreferences.getInt("curndIndex")).thenReturn(null); + when(() => sharedPreferences.getDouble("cameraCalibr")).thenReturn(null); + when(() => sharedPreferences.getDouble("sensorCalibr")).thenReturn(null); + when(() => sharedPreferences.getBool("vibrate")).thenReturn(null); + + when(() => sharedPreferences.remove("curIsoIndex")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("curndIndex")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("cameraCalibr")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("sensorCalibr")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("vibrate")).thenAnswer((_) async => true); + + await service.migrateOldKeys(); + + verifyNever(() => sharedPreferences.remove("curIsoIndex")); + verifyNever(() => sharedPreferences.remove("curndIndex")); + verifyNever(() => sharedPreferences.remove("cameraCalibr")); + verifyNever(() => sharedPreferences.remove("sensorCalibr")); + verifyNever(() => sharedPreferences.remove("vibrate")); + }); + + test('migrate all keys', () async { + when(() => sharedPreferences.getInt("curIsoIndex")).thenReturn(1); + when(() => sharedPreferences.getInt("curndIndex")).thenReturn(0); + when(() => sharedPreferences.getDouble("cameraCalibr")).thenReturn(1.0); + when(() => sharedPreferences.getDouble("sensorCalibr")).thenReturn(-1.0); + when(() => sharedPreferences.getBool("vibrate")).thenReturn(false); + + when( + () => sharedPreferences.setInt(UserPreferencesService.isoKey, IsoValue.values[1].value), + ).thenAnswer((_) => Future.value(true)); + when( + () => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, NdValue.values[0].value), + ).thenAnswer((_) => Future.value(true)); + when( + () => sharedPreferences.setDouble(UserPreferencesService.cameraEvCalibrationKey, 1.0), + ).thenAnswer((_) => Future.value(true)); + when( + () => sharedPreferences.setDouble(UserPreferencesService.lightSensorEvCalibrationKey, -1.0), + ).thenAnswer((_) => Future.value(true)); + when( + () => sharedPreferences.setBool(UserPreferencesService.hapticsKey, false), + ).thenAnswer((_) => Future.value(true)); + + when(() => sharedPreferences.remove("curIsoIndex")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("curndIndex")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("cameraCalibr")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("sensorCalibr")).thenAnswer((_) async => true); + when(() => sharedPreferences.remove("vibrate")).thenAnswer((_) async => true); + + await service.migrateOldKeys(); + + verify(() => sharedPreferences.remove("curIsoIndex")).called(1); + verify(() => sharedPreferences.remove("curndIndex")).called(1); + verify(() => sharedPreferences.remove("cameraCalibr")).called(1); + verify(() => sharedPreferences.remove("sensorCalibr")).called(1); + verify(() => sharedPreferences.remove("vibrate")).called(1); + }); + }); + + group('iso', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.isoKey)).thenReturn(null); + expect(service.iso, const IsoValue(100, StopType.full)); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.isoKey)).thenReturn(100); + expect(service.iso, const IsoValue(100, StopType.full)); + }); + + test('set', () { + when(() => sharedPreferences.setInt(UserPreferencesService.isoKey, 200)) + .thenAnswer((_) => Future.value(true)); + service.iso = const IsoValue(200, StopType.full); + verify(() => sharedPreferences.setInt(UserPreferencesService.isoKey, 200)).called(1); + }); + }); + + group('ndFilter', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.ndFilterKey)).thenReturn(null); + expect(service.ndFilter, const NdValue(0)); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.ndFilterKey)).thenReturn(4); + expect(service.ndFilter, const NdValue(4)); + }); + + test('set', () { + when(() => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, 0)) + .thenAnswer((_) => Future.value(true)); + service.ndFilter = const NdValue(0); + verify(() => sharedPreferences.setInt(UserPreferencesService.ndFilterKey, 0)).called(1); + }); + }); + + group('evSourceType', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.evSourceTypeKey)).thenReturn(null); + expect(service.evSourceType, EvSourceType.camera); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.evSourceTypeKey)).thenReturn(1); + expect(service.evSourceType, EvSourceType.sensor); + }); + + test('set', () { + when(() => sharedPreferences.setInt(UserPreferencesService.evSourceTypeKey, 1)) + .thenAnswer((_) => Future.value(true)); + service.evSourceType = EvSourceType.sensor; + verify(() => sharedPreferences.setInt(UserPreferencesService.evSourceTypeKey, 1)).called(1); + }); + }); + + group('caffeine', () { + test('get default', () { + when(() => sharedPreferences.getBool(UserPreferencesService.caffeineKey)).thenReturn(null); + expect(service.caffeine, false); + }); + + test('get', () { + when(() => sharedPreferences.getBool(UserPreferencesService.caffeineKey)).thenReturn(true); + expect(service.caffeine, true); + }); + + test('set', () { + when(() => sharedPreferences.setBool(UserPreferencesService.caffeineKey, false)) + .thenAnswer((_) => Future.value(true)); + service.caffeine = false; + verify(() => sharedPreferences.setBool(UserPreferencesService.caffeineKey, false)).called(1); + }); + }); + + group('stopType', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.stopTypeKey)).thenReturn(null); + expect(service.stopType, StopType.third); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.stopTypeKey)).thenReturn(1); + expect(service.stopType, StopType.half); + }); + + test('set', () { + when(() => sharedPreferences.setInt(UserPreferencesService.stopTypeKey, 0)) + .thenAnswer((_) => Future.value(true)); + service.stopType = StopType.full; + verify(() => sharedPreferences.setInt(UserPreferencesService.stopTypeKey, 0)).called(1); + }); + }); + + group('meteringScreenLayout', () { + test('get default', () { + when( + () => sharedPreferences.getString(UserPreferencesService.meteringScreenLayoutKey), + ).thenReturn(null); + expect( + service.meteringScreenLayout, + { + MeteringScreenLayoutFeature.extremeExposurePairs: true, + MeteringScreenLayoutFeature.filmPicker: true, + }, + ); + }); + + test('get', () { + when( + () => sharedPreferences.getString(UserPreferencesService.meteringScreenLayoutKey), + ).thenReturn("""{"0":false,"1":true}"""); + expect( + service.meteringScreenLayout, + { + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: true, + }, + ); + }); + + test('set', () { + when( + () => sharedPreferences.setString( + UserPreferencesService.meteringScreenLayoutKey, + """{"0":false,"1":true}""", + ), + ).thenAnswer((_) => Future.value(true)); + service.meteringScreenLayout = { + MeteringScreenLayoutFeature.extremeExposurePairs: false, + MeteringScreenLayoutFeature.filmPicker: true, + }; + verify( + () => sharedPreferences.setString( + UserPreferencesService.meteringScreenLayoutKey, + """{"0":false,"1":true}""", + ), + ).called(1); + }); + }); + + group('haptics', () { + test('get default', () { + when(() => sharedPreferences.getBool(UserPreferencesService.hapticsKey)).thenReturn(null); + expect(service.haptics, true); + }); + + test('get', () { + when(() => sharedPreferences.getBool(UserPreferencesService.hapticsKey)).thenReturn(true); + expect(service.haptics, true); + }); + + test('set', () { + when(() => sharedPreferences.setBool(UserPreferencesService.hapticsKey, false)) + .thenAnswer((_) => Future.value(true)); + service.haptics = false; + verify(() => sharedPreferences.setBool(UserPreferencesService.hapticsKey, false)).called(1); + }); + }); + + group('locale', () { + test('get default', () { + when(() => sharedPreferences.getString(UserPreferencesService.localeKey)).thenReturn(null); + expect(service.locale, SupportedLocale.en); + }); + + test('get', () { + when(() => sharedPreferences.getString(UserPreferencesService.localeKey)) + .thenReturn('SupportedLocale.ru'); + expect(service.locale, SupportedLocale.ru); + }); + + test('set', () { + when( + () => sharedPreferences.setString(UserPreferencesService.localeKey, 'SupportedLocale.en'), + ).thenAnswer((_) => Future.value(true)); + service.locale = SupportedLocale.en; + verify( + () => sharedPreferences.setString(UserPreferencesService.localeKey, 'SupportedLocale.en'), + ).called(1); + }); + }); + + group('cameraEvCalibration', () { + test('get default', () { + when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)) + .thenReturn(null); + expect(service.cameraEvCalibration, 0.0); + }); + + test('get', () { + when(() => sharedPreferences.getDouble(UserPreferencesService.cameraEvCalibrationKey)) + .thenReturn(2.0); + expect(service.cameraEvCalibration, 2.0); + }); + + test('set', () { + when( + () => sharedPreferences.setDouble(UserPreferencesService.cameraEvCalibrationKey, 1.0), + ).thenAnswer((_) => Future.value(true)); + service.cameraEvCalibration = 1.0; + verify( + () => sharedPreferences.setDouble(UserPreferencesService.cameraEvCalibrationKey, 1.0), + ).called(1); + }); + }); + + group('lightSensorEvCalibration', () { + test('get default', () { + when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)) + .thenReturn(null); + expect(service.lightSensorEvCalibration, 0.0); + }); + + test('get', () { + when(() => sharedPreferences.getDouble(UserPreferencesService.lightSensorEvCalibrationKey)) + .thenReturn(2.0); + expect(service.lightSensorEvCalibration, 2.0); + }); + + test('set', () { + when( + () => sharedPreferences.setDouble(UserPreferencesService.lightSensorEvCalibrationKey, 1.0), + ).thenAnswer((_) => Future.value(true)); + service.lightSensorEvCalibration = 1.0; + verify( + () => sharedPreferences.setDouble(UserPreferencesService.lightSensorEvCalibrationKey, 1.0), + ).called(1); + }); + }); + + group('themeType', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.themeTypeKey)).thenReturn(null); + expect(service.themeType, ThemeType.light); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.themeTypeKey)).thenReturn(1); + expect(service.themeType, ThemeType.dark); + }); + + test('set', () { + when( + () => sharedPreferences.setInt(UserPreferencesService.themeTypeKey, 1), + ).thenAnswer((_) => Future.value(true)); + service.themeType = ThemeType.dark; + verify( + () => sharedPreferences.setInt(UserPreferencesService.themeTypeKey, 1), + ).called(1); + }); + }); + + group('primaryColor', () { + test('get default', () { + when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)).thenReturn(null); + expect(service.primaryColor, ThemeProvider.primaryColorsList[5]); + }); + + test('get', () { + when(() => sharedPreferences.getInt(UserPreferencesService.primaryColorKey)) + .thenReturn(0xff9c27b0); + expect(service.primaryColor, ThemeProvider.primaryColorsList[2]); + }); + + test('set', () { + when( + () => sharedPreferences.setInt(UserPreferencesService.primaryColorKey, 0xff000000), + ).thenAnswer((_) => Future.value(true)); + service.primaryColor = Colors.black; + verify( + () => sharedPreferences.setInt(UserPreferencesService.primaryColorKey, 0xff000000), + ).called(1); + }); + }); + + group('dynamicColor', () { + test('get default', () { + when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)) + .thenReturn(null); + expect(service.dynamicColor, false); + }); + + test('get', () { + when(() => sharedPreferences.getBool(UserPreferencesService.dynamicColorKey)) + .thenReturn(true); + expect(service.dynamicColor, true); + }); + + test('set', () { + when(() => sharedPreferences.setBool(UserPreferencesService.dynamicColorKey, false)) + .thenAnswer((_) => Future.value(true)); + service.dynamicColor = false; + verify(() => sharedPreferences.setBool(UserPreferencesService.dynamicColorKey, false)) + .called(1); + }); + }); + + group('film', () { + test('get default', () { + when(() => sharedPreferences.getString(UserPreferencesService.filmKey)).thenReturn(null); + expect(service.film, Film.values.first); + }); + + test('get', () { + when(() => sharedPreferences.getString(UserPreferencesService.filmKey)) + .thenReturn('Fomapan ACTION 400'); + expect(service.film, const FomapanFilm.action400()); + }); + + test('set', () { + when(() => sharedPreferences.setString(UserPreferencesService.filmKey, 'Fomapan ACTION 400')) + .thenAnswer((_) => Future.value(true)); + service.film = const FomapanFilm.action400(); + verify( + () => sharedPreferences.setString(UserPreferencesService.filmKey, 'Fomapan ACTION 400'), + ).called(1); + }); + }); +}