mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 15:30:59 +00:00
Merge branch 'main' of https://github.com/vodemn/m3_lightmeter into feature/ML-61
This commit is contained in:
commit
6d4ad7bc4d
31 changed files with 807 additions and 144 deletions
1
.github/workflows/cd_dev.yml
vendored
1
.github/workflows/cd_dev.yml
vendored
|
@ -71,6 +71,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Prepare flutter project
|
- name: Prepare flutter project
|
||||||
run: |
|
run: |
|
||||||
|
|
1
.github/workflows/cd_prod.yml
vendored
1
.github/workflows/cd_prod.yml
vendored
|
@ -73,6 +73,7 @@ jobs:
|
||||||
uses: subosito/flutter-action@v2
|
uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Prepare flutter project
|
- name: Prepare flutter project
|
||||||
run: |
|
run: |
|
||||||
|
|
30
.github/workflows/ci.yml
vendored
30
.github/workflows/ci.yml
vendored
|
@ -12,18 +12,12 @@ on:
|
||||||
branches: ["main"]
|
branches: ["main"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
analyze_and_test:
|
||||||
|
name: Analyze & test
|
||||||
runs-on: macos-11
|
runs-on: macos-11
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: shaunco/ssh-agent@git-repo-mapping
|
|
||||||
with:
|
|
||||||
ssh-private-key: |
|
|
||||||
${{ secrets.M3_LIGHTMETER_IAP_KEY }}
|
|
||||||
repo-mappings: |
|
|
||||||
github.com/vodemn/m3_lightmeter_iap
|
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
@ -31,23 +25,13 @@ jobs:
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
channel: "stable"
|
||||||
|
flutter-version: '3.10.0'
|
||||||
|
|
||||||
- name: Check flutter version
|
- name: Prepare flutter project
|
||||||
run: flutter --version
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: flutter pub get
|
|
||||||
|
|
||||||
- name: Generate intl
|
|
||||||
run: flutter pub run intl_utils:generate
|
|
||||||
|
|
||||||
- name: Restore firebase_options.dart
|
|
||||||
env:
|
|
||||||
FIREBASE_OPTIONS: ${{ secrets.FIREBASE_OPTIONS }}
|
|
||||||
run: |
|
run: |
|
||||||
FIREBASE_OPTIONS_PATH=$RUNNER_TEMP/firebase_options.dart
|
flutter --version
|
||||||
echo -n "$FIREBASE_OPTIONS" | base64 --decode --output $FIREBASE_OPTIONS_PATH
|
flutter pub get
|
||||||
cp $FIREBASE_OPTIONS_PATH ./lib
|
flutter pub run intl_utils:generate
|
||||||
|
|
||||||
- name: Analyze project source
|
- name: Analyze project source
|
||||||
run: flutter analyze lib --fatal-infos
|
run: flutter analyze lib --fatal-infos
|
||||||
|
|
32
README.md
32
README.md
|
@ -5,7 +5,7 @@
|
||||||
- [Table of contents](#table-of-contents)
|
- [Table of contents](#table-of-contents)
|
||||||
- [Backstory](#backstory)
|
- [Backstory](#backstory)
|
||||||
- [Screenshots](#screenshots)
|
- [Screenshots](#screenshots)
|
||||||
- [Build](#build)
|
- [Development](#development)
|
||||||
- [Contribution](#contribution)
|
- [Contribution](#contribution)
|
||||||
- [iOS Limitations](#ios-limitations)
|
- [iOS Limitations](#ios-limitations)
|
||||||
|
|
||||||
|
@ -27,22 +27,46 @@ Without further delay behold my new Lightmeter app inspired by Material You (a.k
|
||||||
<img src="https://lh3.googleusercontent.com/15g_SPV8knDLFbz1_-wGNJFsJeyVWZ_y--TGHpk75MaaIdMDyTXY2_TL-Aw8bpOhpw" width="18.8%" />
|
<img src="https://lh3.googleusercontent.com/15g_SPV8knDLFbz1_-wGNJFsJeyVWZ_y--TGHpk75MaaIdMDyTXY2_TL-Aw8bpOhpw" width="18.8%" />
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
# Build
|
# Development
|
||||||
|
|
||||||
As part of this project is private, you will be able to run this app from the _main_dev.dart_ file (i.e. --flavor dev). Also to avoid fatal errors the _main_prod.dart_ file is excluded from analysis.
|
### 1. Install Flutter
|
||||||
|
|
||||||
|
To build this app you need to install Flutter 3.10.0 stable. [How to install](https://docs.flutter.dev/get-started/install).
|
||||||
|
|
||||||
|
### 2. (Optional) Install Firebase
|
||||||
|
|
||||||
|
Out of the box Firebase Crashlytics won't work. If you want to add Crashlytics to your local build please follow [this guide](https://firebase.google.com/docs/flutter/setup).
|
||||||
|
|
||||||
|
### 3. Get packages
|
||||||
|
|
||||||
|
Fetch all the neccessary dependencies and generate translation files by running the following commands:
|
||||||
|
```console
|
||||||
|
flutter pub get
|
||||||
|
flutter pub run intl_utils:generate
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Build
|
||||||
|
|
||||||
|
You can build an apk by running the following command from the root of the repository:
|
||||||
|
```console
|
||||||
|
flutter build apk --release --flavor $FLAVOR --dart-define cameraPreviewAspectRatio=2/3 -t lib/main_$FLAVOR.dart
|
||||||
|
```
|
||||||
|
Just replace `$FLAVOR` with `dev` or `prod`.
|
||||||
|
|
||||||
# Contribution
|
# Contribution
|
||||||
|
|
||||||
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
|
To report a bug or suggest a new feature open a new [issue](https://github.com/vodemn/m3_lightmeter/issues).
|
||||||
|
|
||||||
In case you want to help develop this project you need to follow this [style guide](doc/style_guide.md).
|
In case you want to help develop this project feel free to open a Pull Request, but you need to follow this [style guide](doc/style_guide.md).
|
||||||
|
|
||||||
# iOS Limitations
|
# iOS Limitations
|
||||||
|
|
||||||
A list of features, that Android version of the app has and that iOS does not.
|
A list of features, that Android version of the app has and that iOS does not.
|
||||||
|
|
||||||
## Incident light metering
|
## Incident light metering
|
||||||
|
|
||||||
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
|
Apple does not provide API for reading Lux stream form the ambient light sensor. Lux can be calculated based on front camera image stream, but this would be a reflected light. So there is no way incident light metering can be implemented on iOS.
|
||||||
|
|
||||||
## Volume buttons action
|
## Volume buttons action
|
||||||
|
|
||||||
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
This can be [implemented](https://stackoverflow.com/questions/70161271/ios-override-hardware-volume-buttons-same-as-zello) but the app will be rejected due to [2.5.9](https://developer.apple.com/app-store/review/guidelines/#software-requirements)
|
|
@ -1,6 +1,5 @@
|
||||||
include: package:lint/strict.yaml
|
include: package:lint/strict.yaml
|
||||||
|
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
use_setters_to_change_properties: false
|
use_setters_to_change_properties: false
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
enum SupportedLocale { en, fr, ru }
|
enum SupportedLocale { en, fr, ru, zh }
|
||||||
|
|
||||||
extension SupportedLocaleExtension on SupportedLocale {
|
extension SupportedLocaleExtension on SupportedLocale {
|
||||||
String get intlName => toString().replaceAll("SupportedLocale.", "");
|
String get intlName => toString().replaceAll("SupportedLocale.", "");
|
||||||
|
@ -11,6 +11,8 @@ extension SupportedLocaleExtension on SupportedLocale {
|
||||||
return 'Français';
|
return 'Français';
|
||||||
case SupportedLocale.ru:
|
case SupportedLocale.ru:
|
||||||
return 'Русский';
|
return 'Русский';
|
||||||
|
case SupportedLocale.zh:
|
||||||
|
return '简体中文';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -150,6 +150,6 @@ class UserPreferencesService {
|
||||||
String get selectedEquipmentProfileId => ''; // coverage:ignore-line
|
String get selectedEquipmentProfileId => ''; // coverage:ignore-line
|
||||||
set selectedEquipmentProfileId(String id) {} // coverage:ignore-line
|
set selectedEquipmentProfileId(String id) {} // coverage:ignore-line
|
||||||
|
|
||||||
List<EquipmentProfileData> get equipmentProfiles => []; // coverage:ignore-line
|
List<EquipmentProfile> get equipmentProfiles => []; // coverage:ignore-line
|
||||||
set equipmentProfiles(List<EquipmentProfileData> profiles) {} // coverage:ignore-line
|
set equipmentProfiles(List<EquipmentProfile> profiles) {} // coverage:ignore-line
|
||||||
}
|
}
|
||||||
|
|
68
lib/firebase_options.dart
Normal file
68
lib/firebase_options.dart
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// File generated by FlutterFire CLI.
|
||||||
|
// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members
|
||||||
|
import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
|
||||||
|
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
|
||||||
|
|
||||||
|
/// Default [FirebaseOptions] for use with your Firebase apps.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```dart
|
||||||
|
/// import 'firebase_options.dart';
|
||||||
|
/// // ...
|
||||||
|
/// await Firebase.initializeApp(
|
||||||
|
/// options: DefaultFirebaseOptions.currentPlatform,
|
||||||
|
/// );
|
||||||
|
/// ```
|
||||||
|
class DefaultFirebaseOptions {
|
||||||
|
static FirebaseOptions get currentPlatform {
|
||||||
|
if (kIsWeb) {
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for web - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return android;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return ios;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for macos - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for windows - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions have not been configured for linux - '
|
||||||
|
'you can reconfigure this by running the FlutterFire CLI again.',
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw UnsupportedError(
|
||||||
|
'DefaultFirebaseOptions are not supported for this platform.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const FirebaseOptions android = FirebaseOptions(
|
||||||
|
apiKey: '',
|
||||||
|
appId: '',
|
||||||
|
messagingSenderId: '',
|
||||||
|
projectId: '',
|
||||||
|
storageBucket: '',
|
||||||
|
);
|
||||||
|
|
||||||
|
static const FirebaseOptions ios = FirebaseOptions(
|
||||||
|
apiKey: '',
|
||||||
|
appId: '',
|
||||||
|
messagingSenderId: '',
|
||||||
|
projectId: '',
|
||||||
|
storageBucket: '',
|
||||||
|
iosClientId: '',
|
||||||
|
iosBundleId: '',
|
||||||
|
);
|
||||||
|
}
|
|
@ -25,7 +25,9 @@ class MeteringInteractor {
|
||||||
this._permissionsService,
|
this._permissionsService,
|
||||||
this._lightSensorService,
|
this._lightSensorService,
|
||||||
this._volumeEventsService,
|
this._volumeEventsService,
|
||||||
) {
|
);
|
||||||
|
|
||||||
|
void initialize() {
|
||||||
if (_userPreferencesService.caffeine) {
|
if (_userPreferencesService.caffeine) {
|
||||||
_caffeineService.keepScreenOn(true);
|
_caffeineService.keepScreenOn(true);
|
||||||
}
|
}
|
||||||
|
@ -69,7 +71,7 @@ class MeteringInteractor {
|
||||||
.then((value) => value == PermissionStatus.granted);
|
.then((value) => value == PermissionStatus.granted);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestPermission() async {
|
Future<bool> requestCameraPermission() async {
|
||||||
return _permissionsService
|
return _permissionsService
|
||||||
.requestCameraPermission()
|
.requestCameraPermission()
|
||||||
.then((value) => value == PermissionStatus.granted);
|
.then((value) => value == PermissionStatus.granted);
|
||||||
|
|
|
@ -41,8 +41,8 @@ class SettingsInteractor {
|
||||||
|
|
||||||
VolumeAction get volumeAction => _userPreferencesService.volumeAction;
|
VolumeAction get volumeAction => _userPreferencesService.volumeAction;
|
||||||
Future<void> setVolumeAction(VolumeAction value) async {
|
Future<void> setVolumeAction(VolumeAction value) async {
|
||||||
await _volumeEventsService.setVolumeHandling(value != VolumeAction.none);
|
|
||||||
_userPreferencesService.volumeAction = value;
|
_userPreferencesService.volumeAction = value;
|
||||||
|
await _volumeEventsService.setVolumeHandling(value != VolumeAction.none);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get isHapticsEnabled => _userPreferencesService.haptics;
|
bool get isHapticsEnabled => _userPreferencesService.haptics;
|
||||||
|
|
88
lib/l10n/intl_zh.arb
Normal file
88
lib/l10n/intl_zh.arb
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
{
|
||||||
|
"@@locale": "zh",
|
||||||
|
"fastestExposurePair": "最快曝光组合",
|
||||||
|
"slowestExposurePair": "最慢曝光组合",
|
||||||
|
"ev": "EV",
|
||||||
|
"evValue": "{value} EV",
|
||||||
|
"@evValue": {
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"iso": "ISO",
|
||||||
|
"filmSpeed": "胶片感光度",
|
||||||
|
"nd": "ND",
|
||||||
|
"ndFilterFactor": "ND 滤镜系数",
|
||||||
|
"noExposurePairs": "所选设置没有曝光配对",
|
||||||
|
"noCamerasDetected": "您的设备似乎没有连接到任何摄像头",
|
||||||
|
"noCameraPermission": "未获得摄像头权限",
|
||||||
|
"otherCameraError": "连接摄像头时发生错误",
|
||||||
|
"none": "无",
|
||||||
|
"cancel": "取消",
|
||||||
|
"select": "选择",
|
||||||
|
"save": "保存",
|
||||||
|
"settings": "设置",
|
||||||
|
"metering": "测量",
|
||||||
|
"fractionalStops": "EV 步进值",
|
||||||
|
"showFractionalStops": "显示 EV 步进值",
|
||||||
|
"halfStops": "1/2",
|
||||||
|
"thirdStops": "1/3",
|
||||||
|
"calibration": "校准",
|
||||||
|
"calibrationMessage": "此应用测量读数的准确性完全取决于设备的硬件。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||||
|
"calibrationMessageCameraOnly": "此应用程序测量读数的准确性完全取决于设备的后置摄像头。因此,请考虑测试此应用并手动设置 EV 校准,以获得准确的测量结果。",
|
||||||
|
"camera": "摄像头",
|
||||||
|
"lightSensor": "光传感器",
|
||||||
|
"meteringScreenLayout": "布局",
|
||||||
|
"meteringScreenLayoutHint": "隐藏不需要的元素,以免浪费曝光列表空间",
|
||||||
|
"meteringScreenFeatureExtremeExposurePairs": "最快 & 最慢曝光组合",
|
||||||
|
"meteringScreenFeatureFilmPicker": "胶片选择",
|
||||||
|
"film": "胶片",
|
||||||
|
"equipment": "设备",
|
||||||
|
"equipmentProfileName": "设备配置名称",
|
||||||
|
"equipmentProfileNameHint": "Praktica MTL5B",
|
||||||
|
"equipmentProfileAllValues": "全部",
|
||||||
|
"apertureValues": "光圈值",
|
||||||
|
"apertureValuesFilterDescription": "选择要显示的光圈值范围。这通常由您使用的镜头决定。",
|
||||||
|
"ndFilters": "ND 滤镜",
|
||||||
|
"ndFiltersFilterDescription": "选择要显示的 ND 滤镜系数。这些可能是您最常用的 ND 滤镜,也可能是适合您镜头的滤光镜。",
|
||||||
|
"shutterSpeedValues": "快门速度",
|
||||||
|
"shutterSpeedValuesFilterDescription": "选择要显示的快门速度范围。这通常由您使用的相机机身决定。",
|
||||||
|
"isoValues": "ISO",
|
||||||
|
"isoValuesFilterDescription": "选择要显示的 ISO。这些值可能是您最常用的值,也可能是相机支持的值。",
|
||||||
|
"equipmentProfile": "设备配置",
|
||||||
|
"equipmentProfiles": "设备配置",
|
||||||
|
"general": "通用",
|
||||||
|
"keepScreenOn": "保持屏幕常亮",
|
||||||
|
"haptics": "震动",
|
||||||
|
"volumeKeysAction": "音量键快门",
|
||||||
|
"language": "语言",
|
||||||
|
"chooseLanguage": "选择语言",
|
||||||
|
"theme": "主题",
|
||||||
|
"chooseTheme": "选择主题",
|
||||||
|
"themeLight": "亮色",
|
||||||
|
"themeDark": "暗色",
|
||||||
|
"themeSystemDefault": "跟随系统",
|
||||||
|
"dynamicColor": "动态颜色",
|
||||||
|
"primaryColor": "主题颜色",
|
||||||
|
"choosePrimaryColor": "选择主题颜色",
|
||||||
|
"about": "关于",
|
||||||
|
"sourceCode": "源代码",
|
||||||
|
"reportIssue": "报告问题",
|
||||||
|
"writeEmail": "Email",
|
||||||
|
"youDontHaveMailApp": "您没有安装任何邮件App。",
|
||||||
|
"copyEmail": "复制电子邮件",
|
||||||
|
"version": "Version",
|
||||||
|
"versionNumber": "{version} ({buildNumber})",
|
||||||
|
"@versionNumber": {
|
||||||
|
"placeholders": {
|
||||||
|
"version": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"buildNumber": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/application.dart';
|
import 'package:lightmeter/application.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
|
@ -5,6 +7,10 @@ import 'package:lightmeter/firebase.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await initializeFirebase();
|
try {
|
||||||
|
await initializeFirebase();
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
}
|
||||||
runApp(const Application(Environment.prod()));
|
runApp(const Application(Environment.prod()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ import 'package:lightmeter/utils/inherited_generics.dart';
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
typedef EquipmentProfiles = List<EquipmentProfileData>;
|
typedef EquipmentProfiles = List<EquipmentProfile>;
|
||||||
typedef EquipmentProfile = EquipmentProfileData;
|
|
||||||
|
|
||||||
class EquipmentProfileProvider extends StatefulWidget {
|
class EquipmentProfileProvider extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
@ -21,7 +20,7 @@ class EquipmentProfileProvider extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
static const EquipmentProfileData _defaultProfile = EquipmentProfileData(
|
static const EquipmentProfile _defaultProfile = EquipmentProfile(
|
||||||
id: '',
|
id: '',
|
||||||
name: '',
|
name: '',
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
@ -30,10 +29,10 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
isoValues: IsoValue.values,
|
isoValues: IsoValue.values,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<EquipmentProfileData> _customProfiles = [];
|
List<EquipmentProfile> _customProfiles = [];
|
||||||
String _selectedId = '';
|
String _selectedId = '';
|
||||||
|
|
||||||
EquipmentProfileData get _selectedProfile => _customProfiles.firstWhere(
|
EquipmentProfile get _selectedProfile => _customProfiles.firstWhere(
|
||||||
(e) => e.id == _selectedId,
|
(e) => e.id == _selectedId,
|
||||||
orElse: () {
|
orElse: () {
|
||||||
context.get<UserPreferencesService>().selectedEquipmentProfileId = _defaultProfile.id;
|
context.get<UserPreferencesService>().selectedEquipmentProfileId = _defaultProfile.id;
|
||||||
|
@ -50,16 +49,16 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InheritedWidgetBase<List<EquipmentProfileData>>(
|
return InheritedWidgetBase<List<EquipmentProfile>>(
|
||||||
data: [_defaultProfile] + _customProfiles,
|
data: [_defaultProfile] + _customProfiles,
|
||||||
child: InheritedWidgetBase<EquipmentProfileData>(
|
child: InheritedWidgetBase<EquipmentProfile>(
|
||||||
data: _selectedProfile,
|
data: _selectedProfile,
|
||||||
child: widget.child,
|
child: widget.child,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setProfile(EquipmentProfileData data) {
|
void setProfile(EquipmentProfile data) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_selectedId = data.id;
|
_selectedId = data.id;
|
||||||
});
|
});
|
||||||
|
@ -69,7 +68,7 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
/// Creates a default equipment profile
|
/// Creates a default equipment profile
|
||||||
void addProfile(String name) {
|
void addProfile(String name) {
|
||||||
_customProfiles.add(
|
_customProfiles.add(
|
||||||
EquipmentProfileData(
|
EquipmentProfile(
|
||||||
id: const Uuid().v1(),
|
id: const Uuid().v1(),
|
||||||
name: name,
|
name: name,
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
@ -81,7 +80,7 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
_refreshSavedProfiles();
|
_refreshSavedProfiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateProdile(EquipmentProfileData data) {
|
void updateProdile(EquipmentProfile data) {
|
||||||
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
|
final indexToUpdate = _customProfiles.indexWhere((element) => element.id == data.id);
|
||||||
if (indexToUpdate >= 0) {
|
if (indexToUpdate >= 0) {
|
||||||
_customProfiles[indexToUpdate] = data;
|
_customProfiles[indexToUpdate] = data;
|
||||||
|
@ -89,7 +88,7 @@ class EquipmentProfileProviderState extends State<EquipmentProfileProvider> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteProfile(EquipmentProfileData data) {
|
void deleteProfile(EquipmentProfile data) {
|
||||||
_customProfiles.remove(data);
|
_customProfiles.remove(data);
|
||||||
_refreshSavedProfiles();
|
_refreshSavedProfiles();
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@ class Dimens {
|
||||||
static const Duration durationM = Duration(milliseconds: 200);
|
static const Duration durationM = Duration(milliseconds: 200);
|
||||||
static const Duration durationML = Duration(milliseconds: 250);
|
static const Duration durationML = Duration(milliseconds: 250);
|
||||||
static const Duration durationL = Duration(milliseconds: 300);
|
static const Duration durationL = Duration(milliseconds: 300);
|
||||||
|
static const Duration switchDuration = Duration(milliseconds: 100);
|
||||||
|
|
||||||
static const double enabledOpacity = 1.0;
|
static const double enabledOpacity = 1.0;
|
||||||
static const double disabledOpacity = 0.38;
|
static const double disabledOpacity = 0.38;
|
||||||
|
|
|
@ -92,7 +92,7 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onRequestPermission(_, Emitter emit) async {
|
Future<void> _onRequestPermission(_, Emitter emit) async {
|
||||||
final hasPermission = await _meteringInteractor.requestPermission();
|
final hasPermission = await _meteringInteractor.requestCameraPermission();
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
emit(const CameraErrorState(CameraErrorType.permissionNotGranted));
|
emit(const CameraErrorState(CameraErrorType.permissionNotGranted));
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,7 +205,13 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
|
|
||||||
Future<double?> _takePhoto() async {
|
Future<double?> _takePhoto() async {
|
||||||
try {
|
try {
|
||||||
|
// https://github.com/flutter/flutter/issues/84957#issuecomment-1661155095
|
||||||
|
await _cameraController!.setFocusMode(FocusMode.locked);
|
||||||
|
await _cameraController!.setExposureMode(ExposureMode.locked);
|
||||||
final file = await _cameraController!.takePicture();
|
final file = await _cameraController!.takePicture();
|
||||||
|
await _cameraController!.setFocusMode(FocusMode.auto);
|
||||||
|
await _cameraController!.setExposureMode(ExposureMode.auto);
|
||||||
|
|
||||||
final Uint8List bytes = await file.readAsBytes();
|
final Uint8List bytes = await file.readAsBytes();
|
||||||
Directory(file.path).deleteSync(recursive: true);
|
Directory(file.path).deleteSync(recursive: true);
|
||||||
|
|
||||||
|
|
|
@ -10,10 +10,9 @@ class CameraViewPlaceholder extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
color: error != null ? null : Colors.black,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusM)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(Dimens.borderRadiusM)),
|
||||||
child: Center(
|
child: Center(child: error != null ? const Icon(Icons.no_photography) : null),
|
||||||
child: error != null ? const Icon(Icons.no_photography) : const CircularProgressIndicator(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -110,17 +110,17 @@ class _CameraViewBuilder extends StatelessWidget {
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
|
aspectRatio: PlatformConfig.cameraPreviewAspectRatio,
|
||||||
child: BlocBuilder<CameraContainerBloc, CameraContainerState>(
|
child: BlocBuilder<CameraContainerBloc, CameraContainerState>(
|
||||||
buildWhen: (previous, current) =>
|
buildWhen: (previous, current) => current is! CameraActiveState,
|
||||||
current is CameraLoadingState ||
|
builder: (context, state) => Center(
|
||||||
current is CameraInitializedState ||
|
child: AnimatedSwitcher(
|
||||||
current is CameraErrorState,
|
duration: Dimens.durationM,
|
||||||
builder: (context, state) {
|
child: switch (state) {
|
||||||
if (state is CameraInitializedState) {
|
CameraInitializedState() => CameraView(controller: state.controller),
|
||||||
return Center(child: CameraView(controller: state.controller));
|
CameraErrorState() => CameraViewPlaceholder(error: state.error),
|
||||||
} else {
|
_ => const CameraViewPlaceholder(error: null),
|
||||||
return CameraViewPlaceholder(error: state is CameraErrorState ? state.error : null);
|
},
|
||||||
}
|
),
|
||||||
},
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -161,11 +161,11 @@ class _CameraControlsBuilder extends StatelessWidget {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
child = const SizedBox.shrink();
|
child = const Column(children: [Expanded(child: SizedBox.shrink())],);
|
||||||
}
|
}
|
||||||
|
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
duration: Dimens.durationS,
|
duration: Dimens.switchDuration,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,70 +12,72 @@ class ExposurePairsList extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (exposurePairs.isEmpty) {
|
return AnimatedSwitcher(
|
||||||
return const EmptyExposurePairsList();
|
duration: Dimens.switchDuration,
|
||||||
}
|
child: exposurePairs.isEmpty
|
||||||
return Stack(
|
? const EmptyExposurePairsList()
|
||||||
alignment: Alignment.center,
|
: Stack(
|
||||||
children: [
|
|
||||||
Positioned.fill(
|
|
||||||
child: ListView.builder(
|
|
||||||
key: ValueKey(exposurePairs.hashCode),
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingL),
|
|
||||||
itemCount: exposurePairs.length,
|
|
||||||
itemBuilder: (_, index) => Stack(
|
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Positioned.fill(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: ListView.builder(
|
||||||
children: [
|
key: ValueKey(exposurePairs.hashCode),
|
||||||
Expanded(
|
padding: const EdgeInsets.symmetric(vertical: Dimens.paddingL),
|
||||||
child: Align(
|
itemCount: exposurePairs.length,
|
||||||
alignment: Alignment.centerLeft,
|
itemBuilder: (_, index) => Stack(
|
||||||
child: ExposurePairsListItem(
|
alignment: Alignment.center,
|
||||||
exposurePairs[index].aperture,
|
children: [
|
||||||
tickOnTheLeft: false,
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: ExposurePairsListItem(
|
||||||
|
exposurePairs[index].aperture,
|
||||||
|
tickOnTheLeft: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: ExposurePairsListItem(
|
||||||
|
exposurePairs[index].shutterSpeed,
|
||||||
|
tickOnTheLeft: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
Positioned(
|
||||||
),
|
top: 0,
|
||||||
Expanded(
|
bottom: 0,
|
||||||
child: Align(
|
child: LayoutBuilder(
|
||||||
alignment: Alignment.centerLeft,
|
builder: (context, constraints) => Align(
|
||||||
child: ExposurePairsListItem(
|
alignment: index == 0
|
||||||
exposurePairs[index].shutterSpeed,
|
? Alignment.bottomCenter
|
||||||
tickOnTheLeft: true,
|
: (index == exposurePairs.length - 1
|
||||||
|
? Alignment.topCenter
|
||||||
|
: Alignment.center),
|
||||||
|
child: SizedBox(
|
||||||
|
height: index == 0 || index == exposurePairs.length - 1
|
||||||
|
? constraints.maxHeight / 2
|
||||||
|
: constraints.maxHeight,
|
||||||
|
child: ColoredBox(
|
||||||
|
color: Theme.of(context).colorScheme.onBackground,
|
||||||
|
child: const SizedBox(width: 1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Positioned(
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
child: LayoutBuilder(
|
|
||||||
builder: (context, constraints) => Align(
|
|
||||||
alignment: index == 0
|
|
||||||
? Alignment.bottomCenter
|
|
||||||
: (index == exposurePairs.length - 1
|
|
||||||
? Alignment.topCenter
|
|
||||||
: Alignment.center),
|
|
||||||
child: SizedBox(
|
|
||||||
height: index == 0 || index == exposurePairs.length - 1
|
|
||||||
? constraints.maxHeight / 2
|
|
||||||
: constraints.maxHeight,
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Theme.of(context).colorScheme.onBackground,
|
|
||||||
child: const SizedBox(width: 1),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,12 +72,15 @@ class _ReadingValueBuilder extends StatelessWidget {
|
||||||
softWrap: false,
|
softWrap: false,
|
||||||
),
|
),
|
||||||
const SizedBox(height: Dimens.grid4),
|
const SizedBox(height: Dimens.grid4),
|
||||||
Text(
|
AnimatedSwitcher(
|
||||||
reading.value,
|
duration: Dimens.switchDuration,
|
||||||
style: textTheme.titleMedium?.copyWith(color: textColor),
|
child: Text(
|
||||||
maxLines: 1,
|
reading.value,
|
||||||
overflow: TextOverflow.ellipsis,
|
style: textTheme.titleMedium?.copyWith(color: textColor),
|
||||||
softWrap: false,
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
softWrap: false,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -105,7 +105,7 @@ class _EquipmentProfilePicker extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return AnimatedDialogPicker<EquipmentProfileData>(
|
return AnimatedDialogPicker<EquipmentProfile>(
|
||||||
icon: Icons.camera,
|
icon: Icons.camera,
|
||||||
title: S.of(context).equipmentProfile,
|
title: S.of(context).equipmentProfile,
|
||||||
selectedValue: context.listen<EquipmentProfile>(),
|
selectedValue: context.listen<EquipmentProfile>(),
|
||||||
|
|
|
@ -6,7 +6,7 @@ sealed class MeteringEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
class EquipmentProfileChangedEvent extends MeteringEvent {
|
class EquipmentProfileChangedEvent extends MeteringEvent {
|
||||||
final EquipmentProfileData equipmentProfileData;
|
final EquipmentProfile equipmentProfileData;
|
||||||
|
|
||||||
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
const EquipmentProfileChangedEvent(this.equipmentProfileData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ class _MeteringFlowState extends State<MeteringFlow> {
|
||||||
context.get<PermissionsService>(),
|
context.get<PermissionsService>(),
|
||||||
context.get<LightSensorService>(),
|
context.get<LightSensorService>(),
|
||||||
context.get<VolumeEventsService>(),
|
context.get<VolumeEventsService>(),
|
||||||
),
|
)..initialize(),
|
||||||
child: InheritedWidgetBase<VolumeKeysNotifier>(
|
child: InheritedWidgetBase<VolumeKeysNotifier>(
|
||||||
data: VolumeKeysNotifier(context.get<VolumeEventsService>()),
|
data: VolumeKeysNotifier(context.get<VolumeEventsService>()),
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/film.dart';
|
import 'package:lightmeter/data/models/film.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
import 'package:lightmeter/environment.dart';
|
import 'package:lightmeter/environment.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
|
||||||
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
import 'package:lightmeter/providers/ev_source_type_provider.dart';
|
||||||
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
import 'package:lightmeter/screens/metering/bloc_metering.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
|
import 'package:lightmeter/screens/metering/components/bottom_controls/provider_bottom_controls.dart';
|
||||||
|
|
|
@ -8,8 +8,8 @@ import 'package:lightmeter/screens/settings/components/metering/components/equip
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
class EquipmentProfileContainer extends StatefulWidget {
|
class EquipmentProfileContainer extends StatefulWidget {
|
||||||
final EquipmentProfileData data;
|
final EquipmentProfile data;
|
||||||
final ValueChanged<EquipmentProfileData> onUpdate;
|
final ValueChanged<EquipmentProfile> onUpdate;
|
||||||
final VoidCallback onDelete;
|
final VoidCallback onDelete;
|
||||||
final VoidCallback onExpand;
|
final VoidCallback onExpand;
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class EquipmentProfileContainer extends StatefulWidget {
|
||||||
|
|
||||||
class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
late EquipmentProfileData _equipmentData = EquipmentProfileData(
|
late EquipmentProfile _equipmentData = EquipmentProfile(
|
||||||
id: widget.data.id,
|
id: widget.data.id,
|
||||||
name: widget.data.name,
|
name: widget.data.name,
|
||||||
apertureValues: widget.data.apertureValues,
|
apertureValues: widget.data.apertureValues,
|
||||||
|
@ -45,7 +45,7 @@ class EquipmentProfileContainerState extends State<EquipmentProfileContainer>
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(EquipmentProfileContainer oldWidget) {
|
void didUpdateWidget(EquipmentProfileContainer oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
_equipmentData = EquipmentProfileData(
|
_equipmentData = EquipmentProfile(
|
||||||
id: widget.data.id,
|
id: widget.data.id,
|
||||||
name: widget.data.name,
|
name: widget.data.name,
|
||||||
apertureValues: widget.data.apertureValues,
|
apertureValues: widget.data.apertureValues,
|
||||||
|
@ -195,7 +195,7 @@ class _AnimatedArrowButton extends AnimatedWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
class _AnimatedEquipmentListTiles extends AnimatedWidget {
|
||||||
final EquipmentProfileData equipmentData;
|
final EquipmentProfile equipmentData;
|
||||||
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
final ValueChanged<List<ApertureValue>> onApertureValuesSelected;
|
||||||
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
final ValueChanged<List<IsoValue>> onIsoValuesSelecred;
|
||||||
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
final ValueChanged<List<NdValue>> onNdValuesSelected;
|
||||||
|
|
|
@ -84,7 +84,7 @@ class _EquipmentProfilesScreenState extends State<EquipmentProfilesScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateProfileAt(EquipmentProfileData data, int index) {
|
void _updateProfileAt(EquipmentProfile data, int index) {
|
||||||
EquipmentProfileProvider.of(context).updateProdile(data);
|
EquipmentProfileProvider.of(context).updateProdile(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ class EquipmentProfilesListTile extends StatelessWidget {
|
||||||
leading: const Icon(Icons.camera),
|
leading: const Icon(Icons.camera),
|
||||||
title: Text(S.of(context).equipmentProfiles),
|
title: Text(S.of(context).equipmentProfiles),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context).push<EquipmentProfileData>(
|
Navigator.of(context).push<EquipmentProfile>(
|
||||||
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
|
MaterialPageRoute(builder: (_) => const EquipmentProfilesScreen()),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: lightmeter
|
name: lightmeter
|
||||||
description: A new Flutter project.
|
description: A new Flutter project.
|
||||||
publish_to: "none"
|
publish_to: "none"
|
||||||
version: 0.12.0+31
|
version: 0.12.4+35
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
@ -9,7 +9,7 @@ environment:
|
||||||
dependencies:
|
dependencies:
|
||||||
app_settings: 4.2.0
|
app_settings: 4.2.0
|
||||||
bloc_concurrency: 0.2.2
|
bloc_concurrency: 0.2.2
|
||||||
camera: 0.10.5
|
camera: 0.10.5+2
|
||||||
clipboard: 0.1.3
|
clipboard: 0.1.3
|
||||||
dynamic_color: 1.6.5
|
dynamic_color: 1.6.5
|
||||||
exif: 3.1.4
|
exif: 3.1.4
|
||||||
|
|
274
test/interactors/metering_interactor_test.dart
Normal file
274
test/interactors/metering_interactor_test.dart
Normal file
|
@ -0,0 +1,274 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:lightmeter/data/caffeine_service.dart';
|
||||||
|
import 'package:lightmeter/data/haptics_service.dart';
|
||||||
|
import 'package:lightmeter/data/light_sensor_service.dart';
|
||||||
|
import 'package:lightmeter/data/models/film.dart';
|
||||||
|
import 'package:lightmeter/data/models/volume_action.dart';
|
||||||
|
import 'package:lightmeter/data/permissions_service.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
|
import 'package:lightmeter/interactors/metering_interactor.dart';
|
||||||
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
||||||
|
|
||||||
|
class _MockCaffeineService extends Mock implements CaffeineService {}
|
||||||
|
|
||||||
|
class _MockHapticsService extends Mock implements HapticsService {}
|
||||||
|
|
||||||
|
class _MockPermissionsService extends Mock implements PermissionsService {}
|
||||||
|
|
||||||
|
class _MockLightSensorService extends Mock implements LightSensorService {}
|
||||||
|
|
||||||
|
class _MockVolumeEventsService extends Mock implements VolumeEventsService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late _MockUserPreferencesService mockUserPreferencesService;
|
||||||
|
late _MockCaffeineService mockCaffeineService;
|
||||||
|
late _MockHapticsService mockHapticsService;
|
||||||
|
late _MockPermissionsService mockPermissionsService;
|
||||||
|
late _MockLightSensorService mockLightSensorService;
|
||||||
|
late _MockVolumeEventsService mockVolumeEventsService;
|
||||||
|
|
||||||
|
late MeteringInteractor interactor;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockUserPreferencesService = _MockUserPreferencesService();
|
||||||
|
mockCaffeineService = _MockCaffeineService();
|
||||||
|
mockHapticsService = _MockHapticsService();
|
||||||
|
mockPermissionsService = _MockPermissionsService();
|
||||||
|
mockLightSensorService = _MockLightSensorService();
|
||||||
|
mockVolumeEventsService = _MockVolumeEventsService();
|
||||||
|
|
||||||
|
interactor = MeteringInteractor(
|
||||||
|
mockUserPreferencesService,
|
||||||
|
mockCaffeineService,
|
||||||
|
mockHapticsService,
|
||||||
|
mockPermissionsService,
|
||||||
|
mockLightSensorService,
|
||||||
|
mockVolumeEventsService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Initalization',
|
||||||
|
() {
|
||||||
|
test('caffeine - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.caffeine).thenReturn(true);
|
||||||
|
when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true);
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
|
||||||
|
interactor.initialize();
|
||||||
|
verify(() => mockUserPreferencesService.caffeine).called(1);
|
||||||
|
verify(() => mockCaffeineService.keepScreenOn(true)).called(1);
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('caffeine - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.caffeine).thenReturn(false);
|
||||||
|
when(() => mockCaffeineService.keepScreenOn(false)).thenAnswer((_) async => false);
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
|
||||||
|
interactor.initialize();
|
||||||
|
verify(() => mockUserPreferencesService.caffeine).called(1);
|
||||||
|
verifyNever(() => mockCaffeineService.keepScreenOn(false));
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Calibration',
|
||||||
|
() {
|
||||||
|
test('cameraEvCalibration', () async {
|
||||||
|
when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0);
|
||||||
|
expect(interactor.cameraEvCalibration, 0.0);
|
||||||
|
verify(() => mockUserPreferencesService.cameraEvCalibration).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lightSensorEvCalibration', () async {
|
||||||
|
when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0);
|
||||||
|
expect(interactor.lightSensorEvCalibration, 0.0);
|
||||||
|
verify(() => mockUserPreferencesService.lightSensorEvCalibration).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Equipment',
|
||||||
|
() {
|
||||||
|
test('iso - get', () async {
|
||||||
|
when(() => mockUserPreferencesService.iso).thenReturn(IsoValue.values.first);
|
||||||
|
expect(interactor.iso, IsoValue.values.first);
|
||||||
|
verify(() => mockUserPreferencesService.iso).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('iso - set', () async {
|
||||||
|
when(() => mockUserPreferencesService.iso = IsoValue.values.first)
|
||||||
|
.thenReturn(IsoValue.values.first);
|
||||||
|
interactor.iso = IsoValue.values.first;
|
||||||
|
verify(() => mockUserPreferencesService.iso = IsoValue.values.first).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ndFilter - get', () async {
|
||||||
|
when(() => mockUserPreferencesService.ndFilter).thenReturn(NdValue.values.first);
|
||||||
|
expect(interactor.ndFilter, NdValue.values.first);
|
||||||
|
verify(() => mockUserPreferencesService.ndFilter).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('ndFilter - set', () async {
|
||||||
|
when(() => mockUserPreferencesService.ndFilter = NdValue.values.first)
|
||||||
|
.thenReturn(NdValue.values.first);
|
||||||
|
interactor.ndFilter = NdValue.values.first;
|
||||||
|
verify(() => mockUserPreferencesService.ndFilter = NdValue.values.first).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('film - get', () async {
|
||||||
|
when(() => mockUserPreferencesService.film).thenReturn(Film.values.first);
|
||||||
|
expect(interactor.film, Film.values.first);
|
||||||
|
verify(() => mockUserPreferencesService.film).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('film - set', () async {
|
||||||
|
when(() => mockUserPreferencesService.film = Film.values.first)
|
||||||
|
.thenReturn(Film.values.first);
|
||||||
|
interactor.film = Film.values.first;
|
||||||
|
verify(() => mockUserPreferencesService.film = Film.values.first).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Volume action',
|
||||||
|
() {
|
||||||
|
test('volumeAction - VolumeAction.shutter', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||||
|
expect(interactor.volumeAction, VolumeAction.shutter);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('volumeAction - VolumeAction.none', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
|
||||||
|
expect(interactor.volumeAction, VolumeAction.none);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Haptics',
|
||||||
|
() {
|
||||||
|
test('isHapticsEnabled', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
expect(interactor.isHapticsEnabled, true);
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quickVibration() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.quickVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.quickVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quickVibration() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.quickVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.quickVibration());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('responseVibration() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.responseVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.responseVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('responseVibration() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.responseVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.responseVibration());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('errorVibration() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.errorVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.errorVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('errorVibration() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.errorVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.errorVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.errorVibration());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Permissions',
|
||||||
|
() {
|
||||||
|
test('checkCameraPermission() - granted', () async {
|
||||||
|
when(() => mockPermissionsService.checkCameraPermission())
|
||||||
|
.thenAnswer((_) async => PermissionStatus.granted);
|
||||||
|
expectLater(interactor.checkCameraPermission(), completion(true));
|
||||||
|
verify(() => mockPermissionsService.checkCameraPermission()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('checkCameraPermission() - denied', () async {
|
||||||
|
when(() => mockPermissionsService.checkCameraPermission())
|
||||||
|
.thenAnswer((_) async => PermissionStatus.denied);
|
||||||
|
expectLater(interactor.checkCameraPermission(), completion(false));
|
||||||
|
verify(() => mockPermissionsService.checkCameraPermission()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requestCameraPermission() - granted', () async {
|
||||||
|
when(() => mockPermissionsService.requestCameraPermission())
|
||||||
|
.thenAnswer((_) async => PermissionStatus.granted);
|
||||||
|
expectLater(interactor.requestCameraPermission(), completion(true));
|
||||||
|
verify(() => mockPermissionsService.requestCameraPermission()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('requestCameraPermission() - denied', () async {
|
||||||
|
when(() => mockPermissionsService.requestCameraPermission())
|
||||||
|
.thenAnswer((_) async => PermissionStatus.denied);
|
||||||
|
expectLater(interactor.requestCameraPermission(), completion(false));
|
||||||
|
verify(() => mockPermissionsService.requestCameraPermission()).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Haptics',
|
||||||
|
() {
|
||||||
|
test('hasAmbientLightSensor() - true', () async {
|
||||||
|
when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => true);
|
||||||
|
expectLater(interactor.hasAmbientLightSensor(), completion(true));
|
||||||
|
verify(() => mockLightSensorService.hasSensor()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasAmbientLightSensor() - false', () async {
|
||||||
|
when(() => mockLightSensorService.hasSensor()).thenAnswer((_) async => false);
|
||||||
|
expectLater(interactor.hasAmbientLightSensor(), completion(false));
|
||||||
|
verify(() => mockLightSensorService.hasSensor()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('luxStream()', () async {
|
||||||
|
when(() => mockLightSensorService.luxStream()).thenAnswer((_) => const Stream<int>.empty());
|
||||||
|
expect(interactor.luxStream(), const Stream<int>.empty());
|
||||||
|
verify(() => mockLightSensorService.luxStream()).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
205
test/interactors/settings_interactor_test.dart
Normal file
205
test/interactors/settings_interactor_test.dart
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:lightmeter/data/caffeine_service.dart';
|
||||||
|
import 'package:lightmeter/data/haptics_service.dart';
|
||||||
|
import 'package:lightmeter/data/models/volume_action.dart';
|
||||||
|
import 'package:lightmeter/data/shared_prefs_service.dart';
|
||||||
|
import 'package:lightmeter/data/volume_events_service.dart';
|
||||||
|
import 'package:lightmeter/interactors/settings_interactor.dart';
|
||||||
|
import 'package:mocktail/mocktail.dart';
|
||||||
|
|
||||||
|
class _MockUserPreferencesService extends Mock implements UserPreferencesService {}
|
||||||
|
|
||||||
|
class _MockCaffeineService extends Mock implements CaffeineService {}
|
||||||
|
|
||||||
|
class _MockHapticsService extends Mock implements HapticsService {}
|
||||||
|
|
||||||
|
class _MockVolumeEventsService extends Mock implements VolumeEventsService {}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late _MockUserPreferencesService mockUserPreferencesService;
|
||||||
|
late _MockCaffeineService mockCaffeineService;
|
||||||
|
late _MockHapticsService mockHapticsService;
|
||||||
|
late _MockVolumeEventsService mockVolumeEventsService;
|
||||||
|
|
||||||
|
late SettingsInteractor interactor;
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
mockUserPreferencesService = _MockUserPreferencesService();
|
||||||
|
mockCaffeineService = _MockCaffeineService();
|
||||||
|
mockHapticsService = _MockHapticsService();
|
||||||
|
mockVolumeEventsService = _MockVolumeEventsService();
|
||||||
|
|
||||||
|
interactor = SettingsInteractor(
|
||||||
|
mockUserPreferencesService,
|
||||||
|
mockCaffeineService,
|
||||||
|
mockHapticsService,
|
||||||
|
mockVolumeEventsService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Calibration',
|
||||||
|
() {
|
||||||
|
test('cameraEvCalibration - get', () async {
|
||||||
|
when(() => mockUserPreferencesService.cameraEvCalibration).thenReturn(0.0);
|
||||||
|
expect(interactor.cameraEvCalibration, 0.0);
|
||||||
|
verify(() => mockUserPreferencesService.cameraEvCalibration).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('cameraEvCalibration - set', () async {
|
||||||
|
when(() => mockUserPreferencesService.cameraEvCalibration = 0.0).thenReturn(0.0);
|
||||||
|
interactor.setCameraEvCalibration(0.0);
|
||||||
|
verify(() => mockUserPreferencesService.cameraEvCalibration = 0.0).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lightSensorEvCalibration - get', () async {
|
||||||
|
when(() => mockUserPreferencesService.lightSensorEvCalibration).thenReturn(0.0);
|
||||||
|
expect(interactor.lightSensorEvCalibration, 0.0);
|
||||||
|
verify(() => mockUserPreferencesService.lightSensorEvCalibration).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('lightSensorEvCalibration - set', () async {
|
||||||
|
when(() => mockUserPreferencesService.lightSensorEvCalibration = 0.0).thenReturn(0.0);
|
||||||
|
interactor.setLightSensorEvCalibration(0.0);
|
||||||
|
verify(() => mockUserPreferencesService.lightSensorEvCalibration = 0.0).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Caffeine',
|
||||||
|
() {
|
||||||
|
test('isCaffeineEnabled', () async {
|
||||||
|
when(() => mockUserPreferencesService.caffeine).thenReturn(true);
|
||||||
|
expect(interactor.isCaffeineEnabled, true);
|
||||||
|
verify(() => mockUserPreferencesService.caffeine).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enableCaffeine(true)', () async {
|
||||||
|
when(() => mockCaffeineService.keepScreenOn(true)).thenAnswer((_) async => true);
|
||||||
|
when(() => mockUserPreferencesService.caffeine = true).thenReturn(true);
|
||||||
|
await interactor.enableCaffeine(true);
|
||||||
|
verify(() => mockCaffeineService.keepScreenOn(true)).called(1);
|
||||||
|
verify(() => mockUserPreferencesService.caffeine = true).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Volume action',
|
||||||
|
() {
|
||||||
|
test('disableVolumeHandling()', () async {
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
|
||||||
|
expectLater(interactor.disableVolumeHandling(), isA<Future<void>>());
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('restoreVolumeHandling() - VolumeAction.shutter', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
|
||||||
|
expectLater(interactor.restoreVolumeHandling(), isA<Future<void>>());
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('restoreVolumeHandling() - VolumeAction.none', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
|
||||||
|
expectLater(interactor.restoreVolumeHandling(), isA<Future<void>>());
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('volumeAction - VolumeAction.shutter', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.shutter);
|
||||||
|
expect(interactor.volumeAction, VolumeAction.shutter);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('volumeAction - VolumeAction.none', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction).thenReturn(VolumeAction.none);
|
||||||
|
expect(interactor.volumeAction, VolumeAction.none);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setVolumeAction(VolumeAction.shutter)', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter)
|
||||||
|
.thenReturn(VolumeAction.shutter);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(true)).thenAnswer((_) async => true);
|
||||||
|
expectLater(interactor.setVolumeAction(VolumeAction.shutter), isA<Future<void>>());
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(true)).called(1);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction = VolumeAction.shutter).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('setVolumeAction(VolumeAction.none)', () async {
|
||||||
|
when(() => mockUserPreferencesService.volumeAction = VolumeAction.none)
|
||||||
|
.thenReturn(VolumeAction.none);
|
||||||
|
when(() => mockVolumeEventsService.setVolumeHandling(false)).thenAnswer((_) async => false);
|
||||||
|
expectLater(interactor.setVolumeAction(VolumeAction.none), isA<Future<void>>());
|
||||||
|
verify(() => mockVolumeEventsService.setVolumeHandling(false)).called(1);
|
||||||
|
verify(() => mockUserPreferencesService.volumeAction = VolumeAction.none).called(1);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group(
|
||||||
|
'Haptics',
|
||||||
|
() {
|
||||||
|
test('isHapticsEnabled', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
expect(interactor.isHapticsEnabled, true);
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enableHaptics() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics = true).thenReturn(true);
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.enableHaptics(true);
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.quickVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('enableHaptics() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics = false).thenReturn(false);
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.enableHaptics(false);
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.quickVibration());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quickVibration() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.quickVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.quickVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('quickVibration() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.quickVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.quickVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.quickVibration());
|
||||||
|
});
|
||||||
|
|
||||||
|
test('responseVibration() - true', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(true);
|
||||||
|
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.responseVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verify(() => mockHapticsService.responseVibration()).called(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('responseVibration() - false', () async {
|
||||||
|
when(() => mockUserPreferencesService.haptics).thenReturn(false);
|
||||||
|
when(() => mockHapticsService.responseVibration()).thenAnswer((_) async {});
|
||||||
|
interactor.responseVibration();
|
||||||
|
verify(() => mockUserPreferencesService.haptics).called(1);
|
||||||
|
verifyNever(() => mockHapticsService.responseVibration());
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -495,7 +495,7 @@ void main() {
|
||||||
group(
|
group(
|
||||||
'`EquipmentProfileChangedEvent`',
|
'`EquipmentProfileChangedEvent`',
|
||||||
() {
|
() {
|
||||||
final reducedProfile = EquipmentProfileData(
|
final reducedProfile = EquipmentProfile(
|
||||||
id: '0',
|
id: '0',
|
||||||
name: 'Reduced',
|
name: 'Reduced',
|
||||||
apertureValues: ApertureValue.values,
|
apertureValues: ApertureValue.values,
|
||||||
|
|
|
@ -140,11 +140,11 @@ void main() {
|
||||||
'Request denied',
|
'Request denied',
|
||||||
build: () => bloc,
|
build: () => bloc,
|
||||||
setUp: () {
|
setUp: () {
|
||||||
when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => false);
|
when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => false);
|
||||||
},
|
},
|
||||||
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(() => meteringInteractor.requestPermission()).called(1);
|
verify(() => meteringInteractor.requestCameraPermission()).called(1);
|
||||||
},
|
},
|
||||||
expect: () => [
|
expect: () => [
|
||||||
isA<CameraErrorState>()
|
isA<CameraErrorState>()
|
||||||
|
@ -156,12 +156,12 @@ void main() {
|
||||||
'Request granted -> check denied',
|
'Request granted -> check denied',
|
||||||
build: () => bloc,
|
build: () => bloc,
|
||||||
setUp: () {
|
setUp: () {
|
||||||
when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => true);
|
when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => true);
|
||||||
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => false);
|
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => false);
|
||||||
},
|
},
|
||||||
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(() => meteringInteractor.requestPermission()).called(1);
|
verify(() => meteringInteractor.requestCameraPermission()).called(1);
|
||||||
verify(() => meteringInteractor.checkCameraPermission()).called(1);
|
verify(() => meteringInteractor.checkCameraPermission()).called(1);
|
||||||
},
|
},
|
||||||
expect: () => [
|
expect: () => [
|
||||||
|
@ -175,12 +175,12 @@ void main() {
|
||||||
'Request granted -> check granted',
|
'Request granted -> check granted',
|
||||||
build: () => bloc,
|
build: () => bloc,
|
||||||
setUp: () {
|
setUp: () {
|
||||||
when(() => meteringInteractor.requestPermission()).thenAnswer((_) async => true);
|
when(() => meteringInteractor.requestCameraPermission()).thenAnswer((_) async => true);
|
||||||
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
|
when(() => meteringInteractor.checkCameraPermission()).thenAnswer((_) async => true);
|
||||||
},
|
},
|
||||||
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
act: (bloc) => bloc.add(const RequestPermissionEvent()),
|
||||||
verify: (_) {
|
verify: (_) {
|
||||||
verify(() => meteringInteractor.requestPermission()).called(1);
|
verify(() => meteringInteractor.requestCameraPermission()).called(1);
|
||||||
verify(() => meteringInteractor.checkCameraPermission()).called(1);
|
verify(() => meteringInteractor.checkCameraPermission()).called(1);
|
||||||
},
|
},
|
||||||
expect: () => initializedStateSequence,
|
expect: () => initializedStateSequence,
|
||||||
|
|
Loading…
Reference in a new issue