From a7b8de69129254796cd3b311d7e14afdf58dfdf3 Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:29:36 +0200 Subject: [PATCH 1/5] ML-62 Models tests (#80) * removed redundant `UserPreferencesService` from `MeteringBloc` * wip * post-merge fixes * `MeasureEvent` tests * `MeasureEvent` tests revision * `MeasureEvent` tests added timeout * added stubs for other `MeteringBloc` events * rewritten `MeteringBloc` logic * wip * `IsoChangedEvent` tests * refined `IsoChangedEvent` tests * `NdChangedEvent` tests * `FilmChangedEvent` tests * `MeteringCommunicationBloc` tests * added test run to ci * overriden `==` for `MeasuredState` * `LuxMeteringEvent` tests * refined `LuxMeteringEvent` tests * rename * wip * wip * `InitializeEvent`/`DeinitializeEvent` tests * clamp minZoomLevel * fixed `MeteringCommunicationBloc` tests * wip * `ZoomChangedEvent` tests * `ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent` tests * renamed test groups * added test coverage script * improved `CameraContainerBloc` test coverage * `EquipmentProfileChangedEvent` tests * verify response vibration * fixed running all tests * `MeteringCommunicationBloc` equality tests * `CameraContainerBloc` equality tests * removed generated code from coverage * `MeteringScreenLayoutFeature` tests * `SupportedLocale` tests * `Film` tests --- lib/data/models/film.dart | 94 +++++++------- lib/data/models/supported_locale.dart | 15 --- test/data/models/film_test.dart | 121 ++++++++++++++++++ .../metering_screen_layout_config_test.dart | 31 +++++ test/data/models/supported_locale_test.dart | 16 +++ 5 files changed, 216 insertions(+), 61 deletions(-) create mode 100644 test/data/models/film_test.dart create mode 100644 test/data/models/metering_screen_layout_config_test.dart create mode 100644 test/data/models/supported_locale_test.dart 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/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, 'Русский'); + }); +} From 0c581347330199f6e9c201e8581dc7a30c82cf3f Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:35:33 +0200 Subject: [PATCH 2/5] ML-62 Services tests (#82) * removed redundant `UserPreferencesService` from `MeteringBloc` * wip * post-merge fixes * `MeasureEvent` tests * `MeasureEvent` tests revision * `MeasureEvent` tests added timeout * added stubs for other `MeteringBloc` events * rewritten `MeteringBloc` logic * wip * `IsoChangedEvent` tests * refined `IsoChangedEvent` tests * `NdChangedEvent` tests * `FilmChangedEvent` tests * `MeteringCommunicationBloc` tests * added test run to ci * overriden `==` for `MeasuredState` * `LuxMeteringEvent` tests * refined `LuxMeteringEvent` tests * rename * wip * wip * `InitializeEvent`/`DeinitializeEvent` tests * clamp minZoomLevel * fixed `MeteringCommunicationBloc` tests * wip * `ZoomChangedEvent` tests * `ExposureOffsetChangedEvent`/`ExposureOffsetResetEvent` tests * renamed test groups * added test coverage script * improved `CameraContainerBloc` test coverage * `EquipmentProfileChangedEvent` tests * verify response vibration * fixed running all tests * `MeteringCommunicationBloc` equality tests * `CameraContainerBloc` equality tests * removed generated code from coverage * `MeteringScreenLayoutFeature` tests * `SupportedLocale` tests * `Film` tests * `CaffeineService` tests * `UserPreferencesService` tests (wip) * `LightSensorService` tests (wip) * `migrateOldKeys()` tests * ignore currently unused getters & setters * gradle upgrade * `reset(sharedPreferences);` calls count * typo --- android/app/build.gradle | 6 +- android/build.gradle | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- lib/data/caffeine_service.dart | 4 +- lib/data/shared_prefs_service.dart | 91 ++-- test/data/caffeine_service_test.dart | 76 ++++ test/data/light_sensor_service_test.dart | 71 ++++ test/data/shared_prefs_service_test.dart | 392 ++++++++++++++++++ 8 files changed, 590 insertions(+), 54 deletions(-) create mode 100644 test/data/caffeine_service_test.dart create mode 100644 test/data/light_sensor_service_test.dart create mode 100644 test/data/shared_prefs_service_test.dart 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..84e662d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' + classpath 'com.android.tools.build:gradle:7.4.2' classpath 'com.google.gms:google-services:4.3.10' 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/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index a86a7d1..e90df0e 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -10,30 +10,31 @@ 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 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 +70,22 @@ 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); 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 +99,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/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/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart new file mode 100644 index 0000000..6143141 --- /dev/null +++ b/test/data/shared_prefs_service_test.dart @@ -0,0 +1,392 @@ +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('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); + }); + }); +} From 2735f0b66f418ba13492da4dae6c11c69db94a5d Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Fri, 23 Jun 2023 10:47:34 +0200 Subject: [PATCH 3/5] ML-81 Unsaved fractional stops (#83) * save stop type to sharedPrefs * tests --- lib/data/shared_prefs_service.dart | 4 ++++ lib/providers/stop_type_provider.dart | 20 +++++++++++++------- test/data/shared_prefs_service_test.dart | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/data/shared_prefs_service.dart b/lib/data/shared_prefs_service.dart index e90df0e..8fbb6af 100644 --- a/lib/data/shared_prefs_service.dart +++ b/lib/data/shared_prefs_service.dart @@ -14,6 +14,7 @@ class UserPreferencesService { static const ndFilterKey = "ndFilter"; static const evSourceTypeKey = "evSourceType"; + static const stopTypeKey = "stopType"; static const cameraEvCalibrationKey = "cameraEvCalibration"; static const lightSensorEvCalibrationKey = "lightSensorEvCalibration"; static const meteringScreenLayoutKey = "meteringScreenLayout"; @@ -84,6 +85,9 @@ class UserPreferencesService { 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); if (configJson != null) { 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/shared_prefs_service_test.dart b/test/data/shared_prefs_service_test.dart index 6143141..1a2d05b 100644 --- a/test/data/shared_prefs_service_test.dart +++ b/test/data/shared_prefs_service_test.dart @@ -164,6 +164,25 @@ void main() { }); }); + 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( From 8ff387c5c5f37b5d61cfe1ebb46264d516fa6f4b Mon Sep 17 00:00:00 2001 From: Vadim Date: Fri, 23 Jun 2023 11:41:58 +0200 Subject: [PATCH 4/5] Fixed `com.google.gms:google-services` version --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 84e662d..951d253 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -7,7 +7,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.4.2' - classpath 'com.google.gms:google-services:4.3.10' + 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" } From 79d6034894fc9818f50eed8d1d2ad76d715b763d Mon Sep 17 00:00:00 2001 From: Vadim <44135514+vodemn@users.noreply.github.com> Date: Tue, 27 Jun 2023 12:17:35 +0200 Subject: [PATCH 5/5] ML-61 Update version in pubspec & create Github release from GitHub actions (#84) * Replaced zipping action thedoctor0/zip-release@0.7.1 -> vimtor/action-zip@v1.1 * typo * recursive: false * typo * typo * debugSymbolLevel 'FULL' * Update build.gradle * Version bump * wip * wip * `create-release` job * removed changelog input * added `needs` * Version bump * typo * returned to macos-11 runner * reverted pubspec version * Version bump * download artifacts * Version bump * extended artifacts path * Version bump * added LS * Version bump * Version bump * rename files * Version bump * removed ls * Version bump * revert version * typo --- .github/workflows/cd_prod.yml | 63 ++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/.github/workflows/cd_prod.yml b/.github/workflows/cd_prod.yml index f8f0274..f2f8ca2 100644 --- a/.github/workflows/cd_prod.yml +++ b/.github/workflows/cd_prod.yml @@ -7,15 +7,20 @@ name: Build prod .aab & .apk on: workflow_dispatch: + inputs: + version: + description: "Version" + required: true + type: string env: BUILD_ARGS: --release --flavor prod --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_prod.dart jobs: build: + name: Build .apk & .aab runs-on: macos-11 timeout-minutes: 30 - steps: # - uses: shaunco/ssh-agent@git-repo-mapping # with: @@ -61,6 +66,9 @@ jobs: echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH cp $FIREBASE_OPTIONS_PATH ./lib + - name: Increment build number & replace version number + run: perl -i -pe 's/^(version:\s+)(\d+\.\d+\.\d+)(\+)(\d+)$/$1."${{ github.event.inputs.version }}".$3.($4+1)/e' pubspec.yaml + - name: Install Flutter uses: subosito/flutter-action@v2 with: @@ -89,3 +97,56 @@ jobs: with: name: m3_lightmeter_bundle path: build/app/outputs/bundle/prodRelease/app-prod-release.aab + + update-version-in-repo: + name: Update repo version + needs: [build] + runs-on: macos-11 + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Increment build number & replace version number + run: perl -i -pe 's/^(version:\s+)(\d+\.\d+\.\d+)(\+)(\d+)$/$1."${{ github.event.inputs.version }}".$3.($4+1)/e' pubspec.yaml + + - name: Commit and push changes + run: | + git config --global user.name "vodemn" + git config --global user.email "vadim.turko@gmail.com" + + git add -A + git commit -m "Version bump" + git push + + create-release: + name: Create Github release + needs: [build, update-version-in-repo] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Download apk + uses: actions/download-artifact@v3 + with: + name: m3_lightmeter_apk + + - name: Download app bundle + uses: actions/download-artifact@v3 + with: + name: m3_lightmeter_bundle + + - name: Rename artifacts + run: | + mv app-prod-release.apk m3_lightmeter.apk + mv app-prod-release.aab m3_lightmeter.aab + + - uses: ncipollo/release-action@v1.12.0 + with: + artifacts: "m3_lightmeter.apk, m3_lightmeter.aab" + skipIfReleaseExists: true + tag: "v${{ github.event.inputs.version }}"