Merge 7410d7ed60
into f820f9fbba
|
@ -2,20 +2,17 @@ package com.vodemn.lightmeter
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
|
import com.vodemn.lightmeter.PlatformChannels.CaffeinePlatformChannel
|
||||||
|
import com.vodemn.lightmeter.PlatformChannels.CameraInfoPlatformChannel
|
||||||
|
import com.vodemn.lightmeter.PlatformChannels.VolumePlatformChannel
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.EventChannel
|
|
||||||
import io.flutter.plugin.common.EventChannel.EventSink
|
|
||||||
import io.flutter.plugin.common.MethodChannel
|
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
private lateinit var keepScreenOnChannel: MethodChannel
|
private val caffeinePlatformChannel = CaffeinePlatformChannel()
|
||||||
private lateinit var volumeHandlingChannel: MethodChannel
|
private val cameraInfoPlatformChannel = CameraInfoPlatformChannel()
|
||||||
private lateinit var volumeEventChannel: EventChannel
|
private val volumePlatformChannel = VolumePlatformChannel()
|
||||||
private var volumeEventsEmitter: EventSink? = null
|
|
||||||
private var handleVolume = false
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
@ -24,72 +21,24 @@ class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
keepScreenOnChannel = MethodChannel(
|
val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger
|
||||||
flutterEngine.dartExecutor.binaryMessenger,
|
caffeinePlatformChannel.onAttachedToEngine(binaryMessenger, window)
|
||||||
"com.vodemn.lightmeter/keepScreenOn"
|
cameraInfoPlatformChannel.onAttachedToEngine(binaryMessenger, context)
|
||||||
)
|
volumePlatformChannel.onAttachedToEngine(binaryMessenger)
|
||||||
keepScreenOnChannel.setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
|
||||||
"isKeepScreenOn" -> result.success((window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0)
|
|
||||||
"setKeepScreenOn" -> {
|
|
||||||
if (call.arguments !is Boolean) {
|
|
||||||
result.error("invalid args", "Argument should be of type Bool for 'setKeepScreenOn' call", null)
|
|
||||||
} else {
|
|
||||||
if (call.arguments as Boolean) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
||||||
result.success(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeHandlingChannel = MethodChannel(
|
|
||||||
flutterEngine.dartExecutor.binaryMessenger,
|
|
||||||
"com.vodemn.lightmeter/volumeHandling"
|
|
||||||
)
|
|
||||||
volumeHandlingChannel.setMethodCallHandler { call, result ->
|
|
||||||
when (call.method) {
|
|
||||||
"setVolumeHandling" -> {
|
|
||||||
handleVolume = call.arguments as Boolean
|
|
||||||
result.success(handleVolume)
|
|
||||||
}
|
|
||||||
else -> result.notImplemented()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
volumeEventChannel = EventChannel(
|
|
||||||
flutterEngine.dartExecutor.binaryMessenger,
|
|
||||||
"com.vodemn.lightmeter/volumeEvents"
|
|
||||||
)
|
|
||||||
volumeEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
|
||||||
override fun onListen(listener: Any?, eventSink: EventSink) {
|
|
||||||
volumeEventsEmitter = eventSink
|
|
||||||
}
|
|
||||||
override fun onCancel(listener: Any?) {
|
|
||||||
volumeEventsEmitter = null
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
keepScreenOnChannel.setMethodCallHandler(null)
|
caffeinePlatformChannel.onDestroy()
|
||||||
volumeHandlingChannel.setMethodCallHandler(null)
|
cameraInfoPlatformChannel.onDestroy()
|
||||||
volumeEventChannel.setStreamHandler(null)
|
volumePlatformChannel.onDestroy()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onKeyDown(code: Int, event: KeyEvent): Boolean {
|
override fun onKeyDown(code: Int, event: KeyEvent): Boolean {
|
||||||
return when (val keyCode: Int = event.keyCode) {
|
return if (volumePlatformChannel.onKeyDown(code, event)) {
|
||||||
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
true
|
||||||
if (handleVolume) {
|
} else {
|
||||||
volumeEventsEmitter?.success(keyCode)
|
super.onKeyDown(code, event)
|
||||||
true
|
|
||||||
} else {
|
|
||||||
super.onKeyDown(code, event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> super.onKeyDown(code, event)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
package com.vodemn.lightmeter.PlatformChannels
|
||||||
|
|
||||||
|
import android.view.Window
|
||||||
|
import android.view.WindowManager
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
/** CaffeinePlatformChannel */
|
||||||
|
class CaffeinePlatformChannel {
|
||||||
|
private lateinit var channel: MethodChannel
|
||||||
|
|
||||||
|
fun onAttachedToEngine(binaryMessenger: BinaryMessenger, window: Window) {
|
||||||
|
channel = MethodChannel(
|
||||||
|
binaryMessenger,
|
||||||
|
"com.vodemn.lightmeter.CaffeinePlatformChannel.MethodChannel"
|
||||||
|
)
|
||||||
|
channel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"isKeepScreenOn" -> result.success((window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0)
|
||||||
|
"setKeepScreenOn" -> {
|
||||||
|
if (call.arguments !is Boolean) {
|
||||||
|
result.error(
|
||||||
|
"invalid args",
|
||||||
|
"Argument should be of type Bool for 'setKeepScreenOn' call",
|
||||||
|
null
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (call.arguments as Boolean) window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
else window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||||
|
result.success(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDestroy() {
|
||||||
|
channel.setMethodCallHandler(null)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,59 @@
|
||||||
|
package com.vodemn.lightmeter.PlatformChannels
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.hardware.camera2.CameraCharacteristics
|
||||||
|
import android.hardware.camera2.CameraManager
|
||||||
|
import android.hardware.camera2.CameraMetadata.LENS_FACING_BACK
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlin.math.pow
|
||||||
|
import kotlin.math.sqrt
|
||||||
|
|
||||||
|
/** CameraInfoPlatformChannel */
|
||||||
|
class CameraInfoPlatformChannel {
|
||||||
|
private lateinit var channel: MethodChannel
|
||||||
|
private lateinit var cameraManager: CameraManager
|
||||||
|
private var mainCameraEfl: Double? = null
|
||||||
|
|
||||||
|
fun onAttachedToEngine(binaryMessenger: BinaryMessenger, context: Context) {
|
||||||
|
channel = MethodChannel(
|
||||||
|
binaryMessenger,
|
||||||
|
"com.vodemn.lightmeter.CameraInfoPlatformChannel.MethodChannel"
|
||||||
|
)
|
||||||
|
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
|
||||||
|
channel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"mainCameraEfl" -> {
|
||||||
|
mainCameraEfl = mainCameraEfl ?: getMainCameraFocalLength35mm()
|
||||||
|
result.success(mainCameraEfl)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDestroy() {
|
||||||
|
channel.setMethodCallHandler(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getMainCameraFocalLength35mm(): Double? {
|
||||||
|
return cameraManager.cameraIdList.map {
|
||||||
|
cameraManager.getCameraCharacteristics(it)
|
||||||
|
}.first {
|
||||||
|
it.get(CameraCharacteristics.LENS_FACING) == LENS_FACING_BACK
|
||||||
|
}.focalLength35mm()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CameraCharacteristics.focalLength35mm(): Double? {
|
||||||
|
val defaultFocalLength =
|
||||||
|
get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.first()
|
||||||
|
val sensorSize = get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
|
||||||
|
return if (defaultFocalLength != null && sensorSize != null) {
|
||||||
|
// https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length#Conversions
|
||||||
|
43.27 * defaultFocalLength / sqrt(sensorSize.height.pow(2) + sensorSize.width.pow(2))
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.vodemn.lightmeter.PlatformChannels
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.EventChannel.EventSink
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
|
||||||
|
/** VolumePlatformChannel */
|
||||||
|
class VolumePlatformChannel {
|
||||||
|
private lateinit var volumeMethodChannel: MethodChannel
|
||||||
|
private lateinit var volumeEventChannel: EventChannel
|
||||||
|
private var volumeEventsEmitter: EventSink? = null
|
||||||
|
private var handleVolume = false
|
||||||
|
|
||||||
|
fun onAttachedToEngine(binaryMessenger: BinaryMessenger) {
|
||||||
|
volumeMethodChannel = MethodChannel(
|
||||||
|
binaryMessenger,
|
||||||
|
"com.vodemn.lightmeter.VolumePlatformChannel.MethodChannel"
|
||||||
|
)
|
||||||
|
volumeMethodChannel.setMethodCallHandler { call, result ->
|
||||||
|
when (call.method) {
|
||||||
|
"setVolumeHandling" -> {
|
||||||
|
handleVolume = call.arguments as Boolean
|
||||||
|
result.success(handleVolume)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> result.notImplemented()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volumeEventChannel = EventChannel(
|
||||||
|
binaryMessenger,
|
||||||
|
"com.vodemn.lightmeter.VolumePlatformChannel.EventChannel"
|
||||||
|
)
|
||||||
|
volumeEventChannel.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(listener: Any?, eventSink: EventSink) {
|
||||||
|
volumeEventsEmitter = eventSink
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(listener: Any?) {
|
||||||
|
volumeEventsEmitter = null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onDestroy() {
|
||||||
|
volumeMethodChannel.setMethodCallHandler(null)
|
||||||
|
volumeEventChannel.setStreamHandler(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onKeyDown(code: Int, event: KeyEvent): Boolean {
|
||||||
|
return when (val keyCode: Int = event.keyCode) {
|
||||||
|
KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {
|
||||||
|
if (handleVolume) {
|
||||||
|
volumeEventsEmitter?.success(keyCode)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -64,7 +64,7 @@ void testE2E(String description) {
|
||||||
await tester.setApertureValues(mockEquipmentProfiles[0].apertureValues);
|
await tester.setApertureValues(mockEquipmentProfiles[0].apertureValues);
|
||||||
await tester.setShutterSpeedValues(mockEquipmentProfiles[0].shutterSpeedValues);
|
await tester.setShutterSpeedValues(mockEquipmentProfiles[0].shutterSpeedValues);
|
||||||
await tester.setZoomValue(mockEquipmentProfiles[0].lensZoom);
|
await tester.setZoomValue(mockEquipmentProfiles[0].lensZoom);
|
||||||
expect(find.text('x1.91'), findsOneWidget);
|
expect(find.text('50mm'), findsOneWidget);
|
||||||
expect(find.text('f/1.7 - f/16'), findsOneWidget);
|
expect(find.text('f/1.7 - f/16'), findsOneWidget);
|
||||||
expect(find.text('1/1000 - B'), findsOneWidget);
|
expect(find.text('1/1000 - B'), findsOneWidget);
|
||||||
await tester.saveEdits();
|
await tester.saveEdits();
|
||||||
|
@ -77,7 +77,7 @@ void testE2E(String description) {
|
||||||
await tester.enterProfileName(mockEquipmentProfiles[1].name);
|
await tester.enterProfileName(mockEquipmentProfiles[1].name);
|
||||||
await tester.setApertureValues(mockEquipmentProfiles[1].apertureValues);
|
await tester.setApertureValues(mockEquipmentProfiles[1].apertureValues);
|
||||||
await tester.setZoomValue(mockEquipmentProfiles[1].lensZoom);
|
await tester.setZoomValue(mockEquipmentProfiles[1].lensZoom);
|
||||||
expect(find.text('x5.02'), findsOneWidget);
|
expect(find.text('135mm'), findsOneWidget);
|
||||||
expect(find.text('f/3.5 - f/22'), findsOneWidget);
|
expect(find.text('f/3.5 - f/22'), findsOneWidget);
|
||||||
expect(find.text('1/1000 - B'), findsNWidgets(1));
|
expect(find.text('1/1000 - B'), findsNWidgets(1));
|
||||||
await tester.saveEdits();
|
await tester.saveEdits();
|
||||||
|
@ -305,7 +305,8 @@ Future<void> _expectMeteringState(
|
||||||
await tester.scrollToTheLastExposurePair(equipmentProfile: equipmentProfile);
|
await tester.scrollToTheLastExposurePair(equipmentProfile: equipmentProfile);
|
||||||
expectExposurePairsListItem(tester, slowest.split(' - ')[0], slowest.split(' - ')[1]);
|
expectExposurePairsListItem(tester, slowest.split(' - ')[0], slowest.split(' - ')[1]);
|
||||||
expectMeasureButton(ev);
|
expectMeasureButton(ev);
|
||||||
expect(find.text(equipmentProfile.lensZoom.toZoom()), findsOneWidget);
|
final BuildContext context = tester.element(find.byType(IsoValuePicker));
|
||||||
|
expect(find.text(equipmentProfile.lensZoom.toZoom(context)), findsOneWidget);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _expectMeteringStateAndMeasure(
|
Future<void> _expectMeteringStateAndMeasure(
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
import 'package:lightmeter/providers/equipment_profile_provider.dart';
|
||||||
import 'package:lightmeter/providers/films_provider.dart';
|
import 'package:lightmeter/providers/films_provider.dart';
|
||||||
|
@ -115,7 +117,9 @@ final mockEquipmentProfiles = [
|
||||||
IsoValue(1600, StopType.full),
|
IsoValue(1600, StopType.full),
|
||||||
IsoValue(3200, StopType.full),
|
IsoValue(3200, StopType.full),
|
||||||
],
|
],
|
||||||
lensZoom: 1.91,
|
lensZoom: Platform.isAndroid
|
||||||
|
? 2.083333 // Pixel 6
|
||||||
|
: 1.923, // iPhone 13 Pro
|
||||||
),
|
),
|
||||||
EquipmentProfile(
|
EquipmentProfile(
|
||||||
id: '2',
|
id: '2',
|
||||||
|
@ -145,7 +149,9 @@ final mockEquipmentProfiles = [
|
||||||
IsoValue(1600, StopType.full),
|
IsoValue(1600, StopType.full),
|
||||||
IsoValue(3200, StopType.full),
|
IsoValue(3200, StopType.full),
|
||||||
],
|
],
|
||||||
lensZoom: 5.02,
|
lensZoom: Platform.isAndroid
|
||||||
|
? 5.625 // Pixel 6
|
||||||
|
: 5.1923, // iPhone 13 Pro
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import 'e2e_test.dart';
|
import 'e2e_test.dart';
|
||||||
import 'metering_screen_layout_test.dart';
|
import 'metering_screen_layout_test.dart';
|
||||||
import 'purchases_test.dart';
|
import 'purchases_test.dart';
|
||||||
|
import 'utils/platform_channel_mock.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setUpAll(() {
|
||||||
|
mockCameraFocalLength();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() {
|
||||||
|
mockCameraFocalLength();
|
||||||
|
});
|
||||||
|
|
||||||
testPurchases('Purchase & refund premium features');
|
testPurchases('Purchase & refund premium features');
|
||||||
testToggleLayoutFeatures('Toggle metering screen layout features');
|
testToggleLayoutFeatures('Toggle metering screen layout features');
|
||||||
testE2E('e2e');
|
testE2E('e2e');
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:light_sensor/light_sensor.dart';
|
import 'package:light_sensor/light_sensor.dart';
|
||||||
|
import 'package:lightmeter/data/camera_info_service.dart';
|
||||||
|
|
||||||
void setLightSensorAvilability({required bool hasSensor}) {
|
void setLightSensorAvilability({required bool hasSensor}) {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
@ -57,3 +59,26 @@ void resetLightSensorStreamHandler() {
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void mockCameraFocalLength() {
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
CameraInfoService.cameraInfoPlatformChannel,
|
||||||
|
(methodCall) async {
|
||||||
|
switch (methodCall.method) {
|
||||||
|
case "mainCameraEfl":
|
||||||
|
return Platform.isAndroid
|
||||||
|
? 24.0 // Pixel 6
|
||||||
|
: 26.0; // iPhone 13 Pro
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetCameraFocalLength() {
|
||||||
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
|
CameraInfoService.cameraInfoPlatformChannel,
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
001BDA042DD1252B00122957 /* CameraInfoPlatformChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 001BDA022DD0BBA700122957 /* CameraInfoPlatformChannel.swift */; };
|
||||||
|
0035E99F2DD0B07C0053508B /* CaffeinePlatformChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0035E99E2DD0B0700053508B /* CaffeinePlatformChannel.swift */; };
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
5A1AF02F1E4619A6E479AA8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C991C89D5763E562E77E475E /* Pods_Runner.framework */; };
|
5A1AF02F1E4619A6E479AA8B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C991C89D5763E562E77E475E /* Pods_Runner.framework */; };
|
||||||
|
@ -31,6 +33,8 @@
|
||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
001BDA022DD0BBA700122957 /* CameraInfoPlatformChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraInfoPlatformChannel.swift; sourceTree = "<group>"; };
|
||||||
|
0035E99E2DD0B0700053508B /* CaffeinePlatformChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaffeinePlatformChannel.swift; sourceTree = "<group>"; };
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
2913B1E428DD3F7921E898BC /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
2913B1E428DD3F7921E898BC /* GoogleService-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Runner/GoogleService-Info.plist"; sourceTree = "<group>"; };
|
||||||
|
@ -69,6 +73,15 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
0035E99D2DD0B05F0053508B /* PlatformChannels */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
0035E99E2DD0B0700053508B /* CaffeinePlatformChannel.swift */,
|
||||||
|
001BDA022DD0BBA700122957 /* CameraInfoPlatformChannel.swift */,
|
||||||
|
);
|
||||||
|
path = PlatformChannels;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
9740EEB11CF90186004384FC /* Flutter */ = {
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -110,6 +123,7 @@
|
||||||
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
0035E99D2DD0B05F0053508B /* PlatformChannels */,
|
||||||
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
);
|
);
|
||||||
path = Runner;
|
path = Runner;
|
||||||
|
@ -156,7 +170,7 @@
|
||||||
45F53C083F2EA48EF231DA16 /* [CP] Embed Pods Frameworks */,
|
45F53C083F2EA48EF231DA16 /* [CP] Embed Pods Frameworks */,
|
||||||
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */,
|
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */,
|
||||||
08127035D2CDEEEBA66FCDBB /* [CP] Copy Pods Resources */,
|
08127035D2CDEEEBA66FCDBB /* [CP] Copy Pods Resources */,
|
||||||
051083B0AECD5071E5FA2A6F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
|
58ED09C740BAF94D922BAC2C /* FlutterFire: "flutterfire upload-crashlytics-symbols" */,
|
||||||
);
|
);
|
||||||
buildRules = (
|
buildRules = (
|
||||||
);
|
);
|
||||||
|
@ -216,24 +230,6 @@
|
||||||
/* End PBXResourcesBuildPhase section */
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXShellScriptBuildPhase section */
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
051083B0AECD5071E5FA2A6F /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
|
|
||||||
isa = PBXShellScriptBuildPhase;
|
|
||||||
buildActionMask = 2147483647;
|
|
||||||
files = (
|
|
||||||
);
|
|
||||||
inputFileListPaths = (
|
|
||||||
);
|
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
|
|
||||||
outputFileListPaths = (
|
|
||||||
);
|
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
|
||||||
shellPath = /bin/sh;
|
|
||||||
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\"\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
|
|
||||||
};
|
|
||||||
08127035D2CDEEEBA66FCDBB /* [CP] Copy Pods Resources */ = {
|
08127035D2CDEEEBA66FCDBB /* [CP] Copy Pods Resources */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
|
@ -306,6 +302,24 @@
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
showEnvVarsInLog = 0;
|
showEnvVarsInLog = 0;
|
||||||
};
|
};
|
||||||
|
58ED09C740BAF94D922BAC2C /* FlutterFire: "flutterfire upload-crashlytics-symbols" */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 8;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "FlutterFire: \"flutterfire upload-crashlytics-symbols\"";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 1;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\n#!/bin/bash\nPATH=\"${PATH}:$FLUTTER_ROOT/bin:$HOME/.pub-cache/bin\"\nflutterfire upload-crashlytics-symbols --upload-symbols-script-path=\"$PODS_ROOT/FirebaseCrashlytics/upload-symbols\" --platform=ios --apple-project-path=\"${SRCROOT}\" --env-platform-name=\"${PLATFORM_NAME}\" --env-configuration=\"${CONFIGURATION}\" --env-project-dir=\"${PROJECT_DIR}\" --env-built-products-dir=\"${BUILT_PRODUCTS_DIR}\" --env-dwarf-dsym-folder-path=\"${DWARF_DSYM_FOLDER_PATH}\" --env-dwarf-dsym-file-name=\"${DWARF_DSYM_FILE_NAME}\" --env-infoplist-path=\"${INFOPLIST_PATH}\" --default-config=default\n";
|
||||||
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
alwaysOutOfDate = 1;
|
alwaysOutOfDate = 1;
|
||||||
|
@ -319,7 +333,7 @@
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n";
|
||||||
};
|
};
|
||||||
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
|
FF00F85CE432774850A0EDB7 /* [firebase_crashlytics] Crashlytics Upload Symbols */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
@ -352,6 +366,8 @@
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
001BDA042DD1252B00122957 /* CameraInfoPlatformChannel.swift in Sources */,
|
||||||
|
0035E99F2DD0B07C0053508B /* CaffeinePlatformChannel.swift in Sources */,
|
||||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
|
|
@ -8,25 +8,8 @@ import Flutter
|
||||||
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
) -> Bool {
|
) -> Bool {
|
||||||
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||||
let keepScreenOnChannel = FlutterMethodChannel(name: "com.vodemn.lightmeter/keepScreenOn",
|
let caffeinePlatformChannel = CaffeinePlatformChannel(binaryMessenger: controller.binaryMessenger)
|
||||||
binaryMessenger: controller.binaryMessenger)
|
let cameraInfoPlatformChannel = CameraInfoPlatformChannel(binaryMessenger: controller.binaryMessenger)
|
||||||
keepScreenOnChannel.setMethodCallHandler({
|
|
||||||
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
|
||||||
switch call.method {
|
|
||||||
case "isKeepScreenOn":
|
|
||||||
result(UIApplication.shared.isIdleTimerDisabled)
|
|
||||||
case "setKeepScreenOn":
|
|
||||||
guard let keepOn = call.arguments as? Bool else {
|
|
||||||
result(FlutterError(code: "invalid arguments", message: "Argument should be of type Bool for 'setKeepScreenOn' call", details: nil))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UIApplication.shared.isIdleTimerDisabled = keepOn
|
|
||||||
result(true)
|
|
||||||
default:
|
|
||||||
result(FlutterMethodNotImplemented)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
GeneratedPluginRegistrant.register(with: self)
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
}
|
}
|
||||||
|
|
35
ios/Runner/PlatformChannels/CaffeinePlatformChannel.swift
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
//
|
||||||
|
// CaffeinePlatformChannel.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by Vadim Turko on 2025-05-11.
|
||||||
|
//
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
public class CaffeinePlatformChannel: NSObject {
|
||||||
|
let methodChannel: FlutterMethodChannel
|
||||||
|
|
||||||
|
init(binaryMessenger: FlutterBinaryMessenger) {
|
||||||
|
self.methodChannel = FlutterMethodChannel(
|
||||||
|
name: "com.vodemn.lightmeter.CaffeinePlatformChannel.MethodChannel",
|
||||||
|
binaryMessenger: binaryMessenger
|
||||||
|
)
|
||||||
|
super.init()
|
||||||
|
methodChannel.setMethodCallHandler({
|
||||||
|
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||||
|
switch call.method {
|
||||||
|
case "isKeepScreenOn":
|
||||||
|
result(UIApplication.shared.isIdleTimerDisabled)
|
||||||
|
case "setKeepScreenOn":
|
||||||
|
guard let keepOn = call.arguments as? Bool else {
|
||||||
|
result(FlutterError(code: "invalid arguments", message: "Argument should be of type Bool for 'setKeepScreenOn' call", details: nil))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
UIApplication.shared.isIdleTimerDisabled = keepOn
|
||||||
|
result(true)
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
45
ios/Runner/PlatformChannels/CameraInfoPlatformChannel.swift
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
//
|
||||||
|
// CameraInfoPlatformChannel.swift
|
||||||
|
// Runner
|
||||||
|
//
|
||||||
|
// Created by Vadim Turko on 2025-05-11.
|
||||||
|
//
|
||||||
|
import AVFoundation
|
||||||
|
import Flutter
|
||||||
|
|
||||||
|
public class CameraInfoPlatformChannel: NSObject {
|
||||||
|
let methodChannel: FlutterMethodChannel
|
||||||
|
|
||||||
|
init(binaryMessenger: FlutterBinaryMessenger) {
|
||||||
|
self.methodChannel = FlutterMethodChannel(
|
||||||
|
name: "com.vodemn.lightmeter.CameraInfoPlatformChannel.MethodChannel",
|
||||||
|
binaryMessenger: binaryMessenger
|
||||||
|
)
|
||||||
|
super.init()
|
||||||
|
methodChannel.setMethodCallHandler({
|
||||||
|
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
|
||||||
|
switch call.method {
|
||||||
|
case "mainCameraEfl":
|
||||||
|
if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) {
|
||||||
|
result(self.get35mmEquivalentFocalLength(format: device.activeFormat))
|
||||||
|
} else {
|
||||||
|
result(nil)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func get35mmEquivalentFocalLength(format : AVCaptureDevice.Format) -> Float {
|
||||||
|
// get reported field of view. Documentation says this is the horizontal field of view
|
||||||
|
var fov = format.videoFieldOfView
|
||||||
|
// convert to radians
|
||||||
|
fov *= Float.pi/180.0
|
||||||
|
// angle and opposite of right angle triangle are half the fov and half the width of
|
||||||
|
// 35mm film (ie 18mm). The adjacent value of the right angle triangle is the equivalent
|
||||||
|
// focal length. Using some right angle triangle math you can work out focal length
|
||||||
|
let focalLen = 18 / tan(fov/2)
|
||||||
|
return focalLen
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import 'package:flutter_native_splash/flutter_native_splash.dart';
|
||||||
import 'package:lightmeter/data/analytics/analytics.dart';
|
import 'package:lightmeter/data/analytics/analytics.dart';
|
||||||
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
|
import 'package:lightmeter/data/analytics/api/analytics_firebase.dart';
|
||||||
import 'package:lightmeter/data/caffeine_service.dart';
|
import 'package:lightmeter/data/caffeine_service.dart';
|
||||||
|
import 'package:lightmeter/data/camera_info_service.dart';
|
||||||
import 'package:lightmeter/data/haptics_service.dart';
|
import 'package:lightmeter/data/haptics_service.dart';
|
||||||
import 'package:lightmeter/data/light_sensor_service.dart';
|
import 'package:lightmeter/data/light_sensor_service.dart';
|
||||||
import 'package:lightmeter/data/permissions_service.dart';
|
import 'package:lightmeter/data/permissions_service.dart';
|
||||||
|
@ -99,11 +100,13 @@ class _ApplicationWrapperState extends State<ApplicationWrapper> {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
SharedPreferences.getInstance(),
|
SharedPreferences.getInstance(),
|
||||||
const LightSensorService(LocalPlatform()).hasSensor(),
|
const LightSensorService(LocalPlatform()).hasSensor(),
|
||||||
|
const CameraInfoService().mainCameraEfl(),
|
||||||
remoteConfigService.activeAndFetchFeatures(),
|
remoteConfigService.activeAndFetchFeatures(),
|
||||||
equipmentProfilesStorageService.init(),
|
equipmentProfilesStorageService.init(),
|
||||||
filmsStorageService.init(),
|
filmsStorageService.init(),
|
||||||
]).then((value) {
|
]).then((value) {
|
||||||
userPreferencesService = UserPreferencesService((value[0] as SharedPreferences?)!);
|
userPreferencesService = UserPreferencesService((value[0] as SharedPreferences?)!)
|
||||||
|
..cameraFocalLength = value[2] as int?;
|
||||||
hasLightSensor = value[1] as bool? ?? false;
|
hasLightSensor = value[1] as bool? ?? false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class CaffeineService {
|
class CaffeineService {
|
||||||
static const _methodChannel = MethodChannel("com.vodemn.lightmeter/keepScreenOn");
|
@visibleForTesting
|
||||||
|
static const caffeineMethodChannel = MethodChannel("com.vodemn.lightmeter.CaffeinePlatformChannel.MethodChannel");
|
||||||
|
|
||||||
const CaffeineService();
|
const CaffeineService();
|
||||||
|
|
||||||
Future<bool> isKeepScreenOn() async {
|
Future<bool> isKeepScreenOn() async {
|
||||||
return _methodChannel.invokeMethod<bool>("isKeepScreenOn").then((value) => value!);
|
return caffeineMethodChannel.invokeMethod<bool>("isKeepScreenOn").then((value) => value!);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> keepScreenOn(bool keep) async {
|
Future<bool> keepScreenOn(bool keep) async {
|
||||||
return _methodChannel.invokeMethod<bool>("setKeepScreenOn", keep).then((value) => value!);
|
return caffeineMethodChannel.invokeMethod<bool>("setKeepScreenOn", keep).then((value) => value!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
16
lib/data/camera_info_service.dart
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class CameraInfoService {
|
||||||
|
@visibleForTesting
|
||||||
|
static const cameraInfoPlatformChannel = MethodChannel(
|
||||||
|
"com.vodemn.lightmeter.CameraInfoPlatformChannel.MethodChannel",
|
||||||
|
);
|
||||||
|
|
||||||
|
const CameraInfoService();
|
||||||
|
|
||||||
|
Future<int?> mainCameraEfl() async {
|
||||||
|
final focalLength = await cameraInfoPlatformChannel.invokeMethod<double?>('mainCameraEfl');
|
||||||
|
return focalLength?.round();
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,8 +6,8 @@ enum AppFeature {
|
||||||
incidedntLightMetering,
|
incidedntLightMetering,
|
||||||
isoAndNdValues,
|
isoAndNdValues,
|
||||||
themeEngine,
|
themeEngine,
|
||||||
spotMetering,
|
spotMeteringAndHistogram,
|
||||||
histogram,
|
focalLength,
|
||||||
listOfFilms,
|
listOfFilms,
|
||||||
customFilms,
|
customFilms,
|
||||||
equipmentProfiles,
|
equipmentProfiles,
|
||||||
|
@ -28,10 +28,10 @@ enum AppFeature {
|
||||||
return S.of(context).featureIsoAndNdValues;
|
return S.of(context).featureIsoAndNdValues;
|
||||||
case AppFeature.themeEngine:
|
case AppFeature.themeEngine:
|
||||||
return S.of(context).featureTheme;
|
return S.of(context).featureTheme;
|
||||||
case AppFeature.spotMetering:
|
case AppFeature.spotMeteringAndHistogram:
|
||||||
return S.of(context).featureSpotMetering;
|
return S.of(context).featureSpotMeteringAndHistorgram;
|
||||||
case AppFeature.histogram:
|
case AppFeature.focalLength:
|
||||||
return S.of(context).featureHistogram;
|
return S.of(context).featureFocalLength35mm;
|
||||||
case AppFeature.listOfFilms:
|
case AppFeature.listOfFilms:
|
||||||
return S.of(context).featureListOfFilms;
|
return S.of(context).featureListOfFilms;
|
||||||
case AppFeature.customFilms:
|
case AppFeature.customFilms:
|
||||||
|
|
|
@ -1,13 +1,26 @@
|
||||||
enum CameraFeature {
|
enum CameraFeature {
|
||||||
spotMetering,
|
spotMetering,
|
||||||
histogram,
|
histogram,
|
||||||
|
showFocalLength,
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef CameraFeaturesConfig = Map<CameraFeature, bool>;
|
typedef CameraFeaturesConfig = Map<CameraFeature, bool>;
|
||||||
|
|
||||||
extension CameraFeaturesConfigJson on CameraFeaturesConfig {
|
extension CameraFeaturesConfigJson on CameraFeaturesConfig {
|
||||||
static CameraFeaturesConfig fromJson(Map<String, dynamic> data) =>
|
static CameraFeaturesConfig fromJson(Map<String, dynamic> data) {
|
||||||
<CameraFeature, bool>{for (final f in CameraFeature.values) f: data[f.name] as bool? ?? false};
|
MapEntry<CameraFeature, bool> valueOrBool(CameraFeature feature, {bool defaultValue = true}) => MapEntry(
|
||||||
|
feature,
|
||||||
|
data[feature.name] as bool? ?? defaultValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Map.fromEntries(
|
||||||
|
[
|
||||||
|
valueOrBool(CameraFeature.spotMetering),
|
||||||
|
valueOrBool(CameraFeature.histogram, defaultValue: false),
|
||||||
|
valueOrBool(CameraFeature.showFocalLength),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => map((key, value) => MapEntry(key.name, value));
|
Map<String, dynamic> toJson() => map((key, value) => MapEntry(key.name, value));
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ class UserPreferencesService {
|
||||||
static const lightSensorEvCalibrationKey = "lightSensorEvCalibration";
|
static const lightSensorEvCalibrationKey = "lightSensorEvCalibration";
|
||||||
static const meteringScreenLayoutKey = "meteringScreenLayout";
|
static const meteringScreenLayoutKey = "meteringScreenLayout";
|
||||||
static const cameraFeaturesKey = "cameraFeatures";
|
static const cameraFeaturesKey = "cameraFeatures";
|
||||||
|
static const cameraFocalLengthKey = "cameraFocalLength";
|
||||||
|
|
||||||
static const caffeineKey = "caffeine";
|
static const caffeineKey = "caffeine";
|
||||||
static const hapticsKey = "haptics";
|
static const hapticsKey = "haptics";
|
||||||
|
@ -93,38 +94,24 @@ class UserPreferencesService {
|
||||||
set showEv100(bool value) => _sharedPreferences.setBool(showEv100Key, value);
|
set showEv100(bool value) => _sharedPreferences.setBool(showEv100Key, value);
|
||||||
|
|
||||||
MeteringScreenLayoutConfig get meteringScreenLayout {
|
MeteringScreenLayoutConfig get meteringScreenLayout {
|
||||||
final configJson = _sharedPreferences.getString(meteringScreenLayoutKey);
|
final configJson = _sharedPreferences.getString(meteringScreenLayoutKey) ?? '{}';
|
||||||
if (configJson != null) {
|
return MeteringScreenLayoutConfigJson.fromJson(json.decode(configJson) as Map<String, dynamic>);
|
||||||
return MeteringScreenLayoutConfigJson.fromJson(
|
|
||||||
json.decode(configJson) as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
|
||||||
MeteringScreenLayoutFeature.extremeExposurePairs: true,
|
|
||||||
MeteringScreenLayoutFeature.filmPicker: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set meteringScreenLayout(MeteringScreenLayoutConfig value) =>
|
set meteringScreenLayout(MeteringScreenLayoutConfig value) =>
|
||||||
_sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson()));
|
_sharedPreferences.setString(meteringScreenLayoutKey, json.encode(value.toJson()));
|
||||||
|
|
||||||
CameraFeaturesConfig get cameraFeatures {
|
CameraFeaturesConfig get cameraFeatures {
|
||||||
final configJson = _sharedPreferences.getString(cameraFeaturesKey);
|
final configJson = _sharedPreferences.getString(cameraFeaturesKey) ?? '{}';
|
||||||
if (configJson != null) {
|
return CameraFeaturesConfigJson.fromJson(json.decode(configJson) as Map<String, dynamic>);
|
||||||
return CameraFeaturesConfigJson.fromJson(json.decode(configJson) as Map<String, dynamic>);
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
CameraFeature.spotMetering: false,
|
|
||||||
CameraFeature.histogram: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
set cameraFeatures(CameraFeaturesConfig value) =>
|
set cameraFeatures(CameraFeaturesConfig value) =>
|
||||||
_sharedPreferences.setString(cameraFeaturesKey, json.encode(value.toJson()));
|
_sharedPreferences.setString(cameraFeaturesKey, json.encode(value.toJson()));
|
||||||
|
|
||||||
|
int? get cameraFocalLength => _sharedPreferences.getInt(cameraFocalLengthKey);
|
||||||
|
set cameraFocalLength(int? value) => _sharedPreferences.setInt(cameraFocalLengthKey, value!);
|
||||||
|
|
||||||
bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false;
|
bool get caffeine => _sharedPreferences.getBool(caffeineKey) ?? false;
|
||||||
set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value);
|
set caffeine(bool value) => _sharedPreferences.setBool(caffeineKey, value);
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,10 @@ class VolumeEventsService {
|
||||||
final LocalPlatform _localPlatform;
|
final LocalPlatform _localPlatform;
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static const volumeHandlingChannel = MethodChannel("com.vodemn.lightmeter/volumeHandling");
|
static const volumeMethodChannel = MethodChannel("com.vodemn.lightmeter.VolumePlatformChannel.MethodChannel");
|
||||||
|
|
||||||
@visibleForTesting
|
@visibleForTesting
|
||||||
static const volumeEventsChannel = EventChannel("com.vodemn.lightmeter/volumeEvents");
|
static const volumeEventsChannel = EventChannel("com.vodemn.lightmeter.VolumePlatformChannel.EventChannel");
|
||||||
|
|
||||||
const VolumeEventsService(this._localPlatform);
|
const VolumeEventsService(this._localPlatform);
|
||||||
|
|
||||||
|
@ -19,9 +19,7 @@ class VolumeEventsService {
|
||||||
if (!_localPlatform.isAndroid) {
|
if (!_localPlatform.isAndroid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return volumeHandlingChannel
|
return volumeMethodChannel.invokeMethod<bool>("setVolumeHandling", enableHandling).then((value) => value!);
|
||||||
.invokeMethod<bool>("setVolumeHandling", enableHandling)
|
|
||||||
.then((value) => value!);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Emits new events on
|
/// Emits new events on
|
||||||
|
@ -32,9 +30,6 @@ class VolumeEventsService {
|
||||||
if (!_localPlatform.isAndroid) {
|
if (!_localPlatform.isAndroid) {
|
||||||
return const Stream.empty();
|
return const Stream.empty();
|
||||||
}
|
}
|
||||||
return volumeEventsChannel
|
return volumeEventsChannel.receiveBroadcastStream().cast<int>().where((event) => event == 24 || event == 25);
|
||||||
.receiveBroadcastStream()
|
|
||||||
.cast<int>()
|
|
||||||
.where((event) => event == 24 || event == 25);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,8 +30,7 @@ class MeteringInteractor {
|
||||||
if (_userPreferencesService.caffeine) {
|
if (_userPreferencesService.caffeine) {
|
||||||
_caffeineService.keepScreenOn(true);
|
_caffeineService.keepScreenOn(true);
|
||||||
}
|
}
|
||||||
_volumeEventsService
|
_volumeEventsService.setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none);
|
||||||
.setVolumeHandling(_userPreferencesService.volumeAction != VolumeAction.none);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
double get cameraEvCalibration => _userPreferencesService.cameraEvCalibration;
|
double get cameraEvCalibration => _userPreferencesService.cameraEvCalibration;
|
||||||
|
@ -62,15 +61,11 @@ class MeteringInteractor {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> checkCameraPermission() async {
|
Future<bool> checkCameraPermission() async {
|
||||||
return _permissionsService
|
return _permissionsService.checkCameraPermission().then((value) => value == PermissionStatus.granted);
|
||||||
.checkCameraPermission()
|
|
||||||
.then((value) => value == PermissionStatus.granted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> requestCameraPermission() async {
|
Future<bool> requestCameraPermission() async {
|
||||||
return _permissionsService
|
return _permissionsService.requestCameraPermission().then((value) => value == PermissionStatus.granted);
|
||||||
.requestCameraPermission()
|
|
||||||
.then((value) => value == PermissionStatus.granted);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void openAppSettings() {
|
void openAppSettings() {
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"cameraFeatureSpotMeteringHint": "Halte die Kameraansicht gedrückt um den Messpunkt zu entfernen",
|
"cameraFeatureSpotMeteringHint": "Halte die Kameraansicht gedrückt um den Messpunkt zu entfernen",
|
||||||
"cameraFeatureHistogram": "Histogramm",
|
"cameraFeatureHistogram": "Histogramm",
|
||||||
"cameraFeatureHistogramHint": "Verwendung des Histogramms kann den Batterieverbrauch erhöhen",
|
"cameraFeatureHistogramHint": "Verwendung des Histogramms kann den Batterieverbrauch erhöhen",
|
||||||
|
"cameraFeaturesShowFocalLength": "Brennweite anzeigen",
|
||||||
|
"cameraFeaturesShowFocalLengthHint": "Zeigt die 35mm-äquivalente Brennweite statt des Zoomfaktors an",
|
||||||
"film": "Film",
|
"film": "Film",
|
||||||
"filmPush": "Film (push)",
|
"filmPush": "Film (push)",
|
||||||
"filmPull": "Film (pull)",
|
"filmPull": "Film (pull)",
|
||||||
|
@ -111,8 +113,8 @@
|
||||||
"featureIncidentLightMetering": "Messung von einfallendem Licht",
|
"featureIncidentLightMetering": "Messung von einfallendem Licht",
|
||||||
"featureIsoAndNdValues": "Große Auswahl von ISO und ND Filtern",
|
"featureIsoAndNdValues": "Große Auswahl von ISO und ND Filtern",
|
||||||
"featureTheme": "Theme Anpassung",
|
"featureTheme": "Theme Anpassung",
|
||||||
"featureSpotMetering": "Punktmessung",
|
"featureSpotMeteringAndHistorgram": "Spotmessung und Histogramm",
|
||||||
"featureHistogram": "Histogramm",
|
"featureFocalLength35mm": "35mm-äquivalente Brennweite statt Zoom",
|
||||||
"featureListOfFilms": "Liste von 20+ Filmen mit Reziprozitätsformeln",
|
"featureListOfFilms": "Liste von 20+ Filmen mit Reziprozitätsformeln",
|
||||||
"featureCustomFilms": "Eigene Filme erstellen",
|
"featureCustomFilms": "Eigene Filme erstellen",
|
||||||
"featureEquipmentProfiles": "Ausrüstungsprofile",
|
"featureEquipmentProfiles": "Ausrüstungsprofile",
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"cameraFeatureSpotMeteringHint": "Long press the camera view to remove metering spot",
|
"cameraFeatureSpotMeteringHint": "Long press the camera view to remove metering spot",
|
||||||
"cameraFeatureHistogram": "Histogram",
|
"cameraFeatureHistogram": "Histogram",
|
||||||
"cameraFeatureHistogramHint": "Enabling histogram can encrease battery drain",
|
"cameraFeatureHistogramHint": "Enabling histogram can encrease battery drain",
|
||||||
|
"cameraFeaturesShowFocalLength": "Show Focal Length",
|
||||||
|
"cameraFeaturesShowFocalLengthHint": "Displays 35mm equivalent focal length instead of zoom factor",
|
||||||
"film": "Film",
|
"film": "Film",
|
||||||
"filmPush": "Film (push)",
|
"filmPush": "Film (push)",
|
||||||
"filmPull": "Film (pull)",
|
"filmPull": "Film (pull)",
|
||||||
|
@ -111,8 +113,8 @@
|
||||||
"featureIncidentLightMetering": "Incident light metering",
|
"featureIncidentLightMetering": "Incident light metering",
|
||||||
"featureIsoAndNdValues": "Wide range of ISO and ND filters values",
|
"featureIsoAndNdValues": "Wide range of ISO and ND filters values",
|
||||||
"featureTheme": "Theme customization",
|
"featureTheme": "Theme customization",
|
||||||
"featureSpotMetering": "Spot metering",
|
"featureSpotMeteringAndHistorgram": "Spot metering and histogram",
|
||||||
"featureHistogram": "Histogram",
|
"featureFocalLength35mm": "35mm equivalent focal length instead of zoom",
|
||||||
"featureListOfFilms": "List of 20+ films with reciprocity formulas",
|
"featureListOfFilms": "List of 20+ films with reciprocity formulas",
|
||||||
"featureCustomFilms": "Ability to create custom films",
|
"featureCustomFilms": "Ability to create custom films",
|
||||||
"featureEquipmentProfiles": "Equipment profiles",
|
"featureEquipmentProfiles": "Equipment profiles",
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"cameraFeatureSpotMeteringHint": "Appuyez longuement sur la vue de l'appareil photo pour supprimer le spot de mesure",
|
"cameraFeatureSpotMeteringHint": "Appuyez longuement sur la vue de l'appareil photo pour supprimer le spot de mesure",
|
||||||
"cameraFeatureHistogram": "Histogramme",
|
"cameraFeatureHistogram": "Histogramme",
|
||||||
"cameraFeatureHistogramHint": "L'activation de l'histogramme peut augmenter la consommation de la batterie",
|
"cameraFeatureHistogramHint": "L'activation de l'histogramme peut augmenter la consommation de la batterie",
|
||||||
|
"cameraFeaturesShowFocalLength": "Afficher la focale",
|
||||||
|
"cameraFeaturesShowFocalLengthHint": "Affiche la focale équivalente 35 mm au lieu du facteur de zoom",
|
||||||
"film": "Pellicule",
|
"film": "Pellicule",
|
||||||
"filmPush": "Pellicule (push)",
|
"filmPush": "Pellicule (push)",
|
||||||
"filmPull": "Pellicule (pull)",
|
"filmPull": "Pellicule (pull)",
|
||||||
|
@ -112,8 +114,8 @@
|
||||||
"featureIncidentLightMetering": "Mesure de la lumière incidente",
|
"featureIncidentLightMetering": "Mesure de la lumière incidente",
|
||||||
"featureIsoAndNdValues": "Large gamme de valeurs ISO et de filtres ND",
|
"featureIsoAndNdValues": "Large gamme de valeurs ISO et de filtres ND",
|
||||||
"featureTheme": "Personnalisation du thème",
|
"featureTheme": "Personnalisation du thème",
|
||||||
"featureSpotMetering": "Mesure spot",
|
"featureSpotMeteringAndHistorgram": "Mesure spot et histogramme",
|
||||||
"featureHistogram": "Histogramme",
|
"featureFocalLength35mm": "Focale équivalente 35 mm au lieu du zoom",
|
||||||
"featureListOfFilms": "Liste de plus de 20 films avec des formules de correction",
|
"featureListOfFilms": "Liste de plus de 20 films avec des formules de correction",
|
||||||
"featureCustomFilms": "Possibilité de créer des films personnalisés",
|
"featureCustomFilms": "Possibilité de créer des films personnalisés",
|
||||||
"featureEquipmentProfiles": "Profils de l'équipement",
|
"featureEquipmentProfiles": "Profils de l'équipement",
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"cameraFeatureSpotMeteringHint": "Используйте долгое нажатие, чтобы удалить точку замера",
|
"cameraFeatureSpotMeteringHint": "Используйте долгое нажатие, чтобы удалить точку замера",
|
||||||
"cameraFeatureHistogram": "Гистограмма",
|
"cameraFeatureHistogram": "Гистограмма",
|
||||||
"cameraFeatureHistogramHint": "Использование гистограммы может увеличить расход аккумулятора",
|
"cameraFeatureHistogramHint": "Использование гистограммы может увеличить расход аккумулятора",
|
||||||
|
"cameraFeaturesShowFocalLength": "Показать фокусное расстояние",
|
||||||
|
"cameraFeaturesShowFocalLengthHint": "Показывает эквивалент фокусного расстояния (35 мм) вместо коэффициента зума",
|
||||||
"film": "Пленка",
|
"film": "Пленка",
|
||||||
"filmPush": "Пленка (push)",
|
"filmPush": "Пленка (push)",
|
||||||
"filmPull": "Пленка (pull)",
|
"filmPull": "Пленка (pull)",
|
||||||
|
@ -111,8 +113,8 @@
|
||||||
"featureIncidentLightMetering": "Замер падающего света",
|
"featureIncidentLightMetering": "Замер падающего света",
|
||||||
"featureIsoAndNdValues": "Широкий диапазон значений ISO и фильтров ND",
|
"featureIsoAndNdValues": "Широкий диапазон значений ISO и фильтров ND",
|
||||||
"featureTheme": "Настройка темы",
|
"featureTheme": "Настройка темы",
|
||||||
"featureSpotMetering": "Точечный замер",
|
"featureSpotMeteringAndHistorgram": "Точечный замер и гистограмма",
|
||||||
"featureHistogram": "Гистограмма",
|
"featureFocalLength35mm": "Эквивалентное фокусное расстояние 35 мм вместо зума",
|
||||||
"featureListOfFilms": "Список из 20+ плёнок с формулами коррекции",
|
"featureListOfFilms": "Список из 20+ плёнок с формулами коррекции",
|
||||||
"featureCustomFilms": "Возможность создания собственных плёнок",
|
"featureCustomFilms": "Возможность создания собственных плёнок",
|
||||||
"featureEquipmentProfiles": "Профили оборудования",
|
"featureEquipmentProfiles": "Профили оборудования",
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
"cameraFeatureSpotMeteringHint": "长按相机视图可移除测光点",
|
"cameraFeatureSpotMeteringHint": "长按相机视图可移除测光点",
|
||||||
"cameraFeatureHistogram": "直方图",
|
"cameraFeatureHistogram": "直方图",
|
||||||
"cameraFeatureHistogramHint": "启用直方图会增加电池消耗",
|
"cameraFeatureHistogramHint": "启用直方图会增加电池消耗",
|
||||||
|
"cameraFeaturesShowFocalLength": "显示焦距",
|
||||||
|
"cameraFeaturesShowFocalLengthHint": "显示 35mm 等效焦距而非变焦倍数",
|
||||||
"film": "胶片",
|
"film": "胶片",
|
||||||
"filmPush": "胶片 (push)",
|
"filmPush": "胶片 (push)",
|
||||||
"filmPull": "胶片 (pull)",
|
"filmPull": "胶片 (pull)",
|
||||||
|
@ -110,8 +112,8 @@
|
||||||
"featureIncidentLightMetering": "入射光测光",
|
"featureIncidentLightMetering": "入射光测光",
|
||||||
"featureIsoAndNdValues": "更广的 ISO 和 ND 滤镜系数范围",
|
"featureIsoAndNdValues": "更广的 ISO 和 ND 滤镜系数范围",
|
||||||
"featureTheme": "主题自定义",
|
"featureTheme": "主题自定义",
|
||||||
"featureSpotMetering": "点测光",
|
"featureSpotMeteringAndHistorgram": "点测光与直方图",
|
||||||
"featureHistogram": "直方图",
|
"featureFocalLength35mm": "35mm 等效焦距替代变焦",
|
||||||
"featureListOfFilms": "20多种胶片的补偿公式",
|
"featureListOfFilms": "20多种胶片的补偿公式",
|
||||||
"featureCustomFilms": "创建自定义胶片",
|
"featureCustomFilms": "创建自定义胶片",
|
||||||
"featureEquipmentProfiles": "设备配置文件",
|
"featureEquipmentProfiles": "设备配置文件",
|
||||||
|
@ -150,4 +152,4 @@
|
||||||
"filmFormulaExponentialRfPlaceholder": "1.3",
|
"filmFormulaExponentialRfPlaceholder": "1.3",
|
||||||
"addEquipmentProfileTitle": "添加设备",
|
"addEquipmentProfileTitle": "添加设备",
|
||||||
"editEquipmentProfileTitle": "编辑设备"
|
"editEquipmentProfileTitle": "编辑设备"
|
||||||
}
|
}
|
|
@ -247,7 +247,7 @@ class _LensZoomListTileBuilder extends StatelessWidget {
|
||||||
description: S.of(context).lensZoomDescription,
|
description: S.of(context).lensZoomDescription,
|
||||||
value: state.lensZoom,
|
value: state.lensZoom,
|
||||||
range: const RangeValues(1, 7),
|
range: const RangeValues(1, 7),
|
||||||
valueAdapter: (_, value) => value.toZoom(),
|
valueAdapter: (context, value) => value.toZoom(context),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileLensZoomChangedEvent(value));
|
context.read<EquipmentProfileEditBloc>().add(EquipmentProfileLensZoomChangedEvent(value));
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'dart:io';
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
@ -17,7 +18,7 @@ import 'package:lightmeter/screens/metering/components/camera_container/event_co
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/models/camera_error_type.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
import 'package:lightmeter/screens/metering/components/camera_container/state_container_camera.dart';
|
||||||
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
import 'package:lightmeter/screens/metering/components/shared/ev_source_base/bloc_base_ev_source.dart';
|
||||||
import 'package:lightmeter/utils/ev_from_bytes.dart';
|
import 'package:lightmeter/utils/exif_utils.dart';
|
||||||
|
|
||||||
part 'mock_bloc_container_camera.part.dart';
|
part 'mock_bloc_container_camera.part.dart';
|
||||||
|
|
||||||
|
@ -184,7 +185,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
_cameraController = cameraController;
|
_cameraController = cameraController;
|
||||||
emit(CameraInitializedState(cameraController));
|
emit(CameraInitializedState(cameraController));
|
||||||
_emitActiveState(emit);
|
_emitActiveState(emit);
|
||||||
} catch (e) {
|
} catch (e, stackTrace) {
|
||||||
|
_analytics.logCrash(e, stackTrace);
|
||||||
emit(const CameraErrorState(CameraErrorType.other));
|
emit(const CameraErrorState(CameraErrorType.other));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -247,7 +249,8 @@ class CameraContainerBloc extends EvSourceBlocBase<CameraContainerEvent, CameraC
|
||||||
final file = await _cameraController!.takePicture();
|
final file = await _cameraController!.takePicture();
|
||||||
final bytes = await file.readAsBytes();
|
final bytes = await file.readAsBytes();
|
||||||
Directory(file.path).deleteSync(recursive: true);
|
Directory(file.path).deleteSync(recursive: true);
|
||||||
return await evFromImage(bytes);
|
final tags = await readExifFromBytes(bytes);
|
||||||
|
return evFromTags(tags);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_analytics.logCrash(e, stackTrace);
|
_analytics.logCrash(e, stackTrace);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -24,7 +24,7 @@ class ZoomSlider extends StatelessWidget {
|
||||||
icon: Icons.search_outlined,
|
icon: Icons.search_outlined,
|
||||||
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
|
defaultValue: EquipmentProfiles.selectedOf(context).lensZoom,
|
||||||
rulerValueAdapter: (value) => value.toStringAsFixed(0),
|
rulerValueAdapter: (value) => value.toStringAsFixed(0),
|
||||||
valueAdapter: (value) => value.toZoom(),
|
valueAdapter: (value) => value.toZoom(context),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,8 @@ class MockCameraContainerBloc extends CameraContainerBloc {
|
||||||
Future<double?> _takePhoto() async {
|
Future<double?> _takePhoto() async {
|
||||||
try {
|
try {
|
||||||
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
final bytes = (await rootBundle.load(PlatformConfig.cameraStubImage)).buffer.asUint8List();
|
||||||
return await evFromImage(bytes);
|
final tags = await readExifFromBytes(bytes);
|
||||||
|
return evFromTags(tags);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
log(e.toString(), stackTrace: stackTrace);
|
log(e.toString(), stackTrace: stackTrace);
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lightmeter/data/models/camera_feature.dart';
|
import 'package:lightmeter/data/models/camera_feature.dart';
|
||||||
import 'package:lightmeter/generated/l10n.dart';
|
import 'package:lightmeter/generated/l10n.dart';
|
||||||
|
import 'package:lightmeter/providers/services_provider.dart';
|
||||||
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
|
import 'package:lightmeter/screens/settings/components/shared/dialog_switch/widget_dialog_switch.dart';
|
||||||
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
import 'package:lightmeter/screens/settings/components/shared/iap_list_tile/widget_list_tile_iap.dart';
|
||||||
|
@ -20,21 +21,21 @@ class CameraFeaturesListTile extends StatelessWidget {
|
||||||
icon: Icons.camera_alt_outlined,
|
icon: Icons.camera_alt_outlined,
|
||||||
title: S.of(context).cameraFeatures,
|
title: S.of(context).cameraFeatures,
|
||||||
values: UserPreferencesProvider.cameraConfigOf(context),
|
values: UserPreferencesProvider.cameraConfigOf(context),
|
||||||
titleAdapter: (context, feature) {
|
enabledAdapter: (feature) => switch (feature) {
|
||||||
switch (feature) {
|
CameraFeature.spotMetering => true,
|
||||||
case CameraFeature.spotMetering:
|
CameraFeature.histogram => true,
|
||||||
return S.of(context).cameraFeatureSpotMetering;
|
CameraFeature.showFocalLength =>
|
||||||
case CameraFeature.histogram:
|
ServicesProvider.of(context).userPreferencesService.cameraFocalLength != null,
|
||||||
return S.of(context).cameraFeatureHistogram;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
subtitleAdapter: (context, feature) {
|
titleAdapter: (context, feature) => switch (feature) {
|
||||||
switch (feature) {
|
CameraFeature.spotMetering => S.of(context).cameraFeatureSpotMetering,
|
||||||
case CameraFeature.spotMetering:
|
CameraFeature.histogram => S.of(context).cameraFeatureHistogram,
|
||||||
return S.of(context).cameraFeatureSpotMeteringHint;
|
CameraFeature.showFocalLength => S.of(context).cameraFeaturesShowFocalLength,
|
||||||
case CameraFeature.histogram:
|
},
|
||||||
return S.of(context).cameraFeatureHistogramHint;
|
subtitleAdapter: (context, feature) => switch (feature) {
|
||||||
}
|
CameraFeature.spotMetering => S.of(context).cameraFeatureSpotMeteringHint,
|
||||||
|
CameraFeature.histogram => S.of(context).cameraFeatureHistogramHint,
|
||||||
|
CameraFeature.showFocalLength => S.of(context).cameraFeaturesShowFocalLengthHint,
|
||||||
},
|
},
|
||||||
onSave: UserPreferencesProvider.of(context).setCameraFeature,
|
onSave: UserPreferencesProvider.of(context).setCameraFeature,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,3 +1,19 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/models/camera_feature.dart';
|
||||||
|
import 'package:lightmeter/providers/services_provider.dart';
|
||||||
|
import 'package:lightmeter/providers/user_preferences_provider.dart';
|
||||||
|
|
||||||
extension DoubleToZoom on double {
|
extension DoubleToZoom on double {
|
||||||
String toZoom() => 'x${toStringAsFixed(2)}';
|
String toZoom(BuildContext context) {
|
||||||
|
final showFocalLength = UserPreferencesProvider.cameraFeatureOf(context, CameraFeature.showFocalLength);
|
||||||
|
final cameraFocalLength = ServicesProvider.of(context).userPreferencesService.cameraFocalLength;
|
||||||
|
|
||||||
|
if (showFocalLength && cameraFocalLength != null) {
|
||||||
|
ServicesProvider.of(context).userPreferencesService.cameraFocalLength;
|
||||||
|
final zoomedFocalLength = (this * cameraFocalLength).round();
|
||||||
|
return '${zoomedFocalLength}mm';
|
||||||
|
} else {
|
||||||
|
return 'x${toStringAsFixed(2)}';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,20 @@
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
import 'package:exif/exif.dart';
|
import 'package:exif/exif.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
|
|
||||||
const String _isoExifKey = 'EXIF ISOSpeedRatings';
|
const String _isoExifKey = 'EXIF ISOSpeedRatings';
|
||||||
const String _apertureExifKey = 'EXIF FNumber';
|
const String _apertureExifKey = 'EXIF FNumber';
|
||||||
const String _shutterSpeedExifKey = 'EXIF ExposureTime';
|
const String _shutterSpeedExifKey = 'EXIF ExposureTime';
|
||||||
|
|
||||||
Future<double> evFromImage(Uint8List bytes) async {
|
double evFromTags(Map<String, IfdTag> tags) {
|
||||||
final tags = await readExifFromBytes(bytes);
|
|
||||||
final iso = double.tryParse("${tags[_isoExifKey]}");
|
final iso = double.tryParse("${tags[_isoExifKey]}");
|
||||||
final apertureValueRatio = (tags[_apertureExifKey]?.values as IfdRatios?)?.ratios.first;
|
final apertureValueRatio = (tags[_apertureExifKey]?.values as IfdRatios?)?.ratios.first;
|
||||||
final speedValueRatio = (tags[_shutterSpeedExifKey]?.values as IfdRatios?)?.ratios.first;
|
final speedValueRatio = (tags[_shutterSpeedExifKey]?.values as IfdRatios?)?.ratios.first;
|
||||||
|
|
||||||
if (iso == null || apertureValueRatio == null || speedValueRatio == null) {
|
if (iso == null || apertureValueRatio == null || speedValueRatio == null) {
|
||||||
throw ArgumentError(
|
throw ArgumentError(
|
||||||
'Error parsing EXIF',
|
'Error calculating EV',
|
||||||
[
|
[
|
||||||
if (iso == null) '$_isoExifKey: ${tags[_isoExifKey]?.printable} ${tags[_isoExifKey]?.printable.runtimeType}',
|
if (iso == null) '$_isoExifKey: ${tags[_isoExifKey]?.printable} ${tags[_isoExifKey]?.printable.runtimeType}',
|
||||||
if (apertureValueRatio == null) '$_apertureExifKey: $apertureValueRatio',
|
if (apertureValueRatio == null) '$_apertureExifKey: $apertureValueRatio',
|
|
@ -52,7 +52,7 @@ Apple requires screenshots a specific list of devices, so we can implement a cus
|
||||||
Can be run on Simulator.
|
Can be run on Simulator.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
sh screenshots/generate_ios_screenshots.sh
|
sh screenshots/scripts/generate_ios_screenshots.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### Apply store constraints and text data
|
### Apply store constraints and text data
|
||||||
|
|
|
@ -6,6 +6,7 @@ import 'dart:io';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:lightmeter/data/models/camera_feature.dart';
|
||||||
import 'package:lightmeter/data/models/ev_source_type.dart';
|
import 'package:lightmeter/data/models/ev_source_type.dart';
|
||||||
import 'package:lightmeter/data/models/exposure_pair.dart';
|
import 'package:lightmeter/data/models/exposure_pair.dart';
|
||||||
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
import 'package:lightmeter/data/models/metering_screen_layout_config.dart';
|
||||||
|
@ -27,6 +28,7 @@ import 'package:m3_lightmeter_resources/m3_lightmeter_resources.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import '../integration_test/mocks/paid_features_mock.dart';
|
import '../integration_test/mocks/paid_features_mock.dart';
|
||||||
|
import '../integration_test/utils/platform_channel_mock.dart';
|
||||||
import '../integration_test/utils/widget_tester_actions.dart';
|
import '../integration_test/utils/widget_tester_actions.dart';
|
||||||
import 'models/screenshot_args.dart';
|
import 'models/screenshot_args.dart';
|
||||||
|
|
||||||
|
@ -68,6 +70,14 @@ void main() {
|
||||||
}.toJson(),
|
}.toJson(),
|
||||||
),
|
),
|
||||||
|
|
||||||
|
UserPreferencesService.cameraFeaturesKey: json.encode(
|
||||||
|
{
|
||||||
|
CameraFeature.spotMetering: false,
|
||||||
|
CameraFeature.histogram: false,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
|
}.toJson(),
|
||||||
|
),
|
||||||
|
|
||||||
/// General settings
|
/// General settings
|
||||||
UserPreferencesService.autostartTimerKey: false,
|
UserPreferencesService.autostartTimerKey: false,
|
||||||
UserPreferencesService.caffeineKey: true,
|
UserPreferencesService.caffeineKey: true,
|
||||||
|
@ -86,6 +96,11 @@ void main() {
|
||||||
|
|
||||||
setUpAll(() async {
|
setUpAll(() async {
|
||||||
if (Platform.isAndroid) await binding.convertFlutterSurfaceToImage();
|
if (Platform.isAndroid) await binding.convertFlutterSurfaceToImage();
|
||||||
|
mockCameraFocalLength();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() {
|
||||||
|
resetCameraFocalLength();
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Generates several screenshots with the light theme
|
/// Generates several screenshots with the light theme
|
||||||
|
@ -119,6 +134,9 @@ void main() {
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.tap(find.byIcon(Icons.edit_outlined).first);
|
await tester.tap(find.byIcon(Icons.edit_outlined).first);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tap(find.text(S.current.isoValues)); // open and close a dialog to hide keyboard
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
await tester.tapCancelButton();
|
||||||
await tester.takeScreenshotLight(binding, 'equipment-profiles');
|
await tester.takeScreenshotLight(binding, 'equipment-profiles');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 469 KiB After Width: | Height: | Size: 469 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 223 KiB After Width: | Height: | Size: 223 KiB |
Before Width: | Height: | Size: 478 KiB After Width: | Height: | Size: 478 KiB |
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 220 KiB |
Before Width: | Height: | Size: 348 KiB After Width: | Height: | Size: 348 KiB |
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 181 KiB After Width: | Height: | Size: 180 KiB |
Before Width: | Height: | Size: 347 KiB After Width: | Height: | Size: 347 KiB |
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 169 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 148 KiB |
Before Width: | Height: | Size: 511 KiB After Width: | Height: | Size: 511 KiB |
Before Width: | Height: | Size: 224 KiB After Width: | Height: | Size: 224 KiB |
Before Width: | Height: | Size: 254 KiB After Width: | Height: | Size: 253 KiB |
Before Width: | Height: | Size: 505 KiB After Width: | Height: | Size: 504 KiB |
Before Width: | Height: | Size: 238 KiB After Width: | Height: | Size: 239 KiB |
Before Width: | Height: | Size: 207 KiB After Width: | Height: | Size: 207 KiB |
|
@ -7,7 +7,6 @@ void main() {
|
||||||
|
|
||||||
late CaffeineService service;
|
late CaffeineService service;
|
||||||
|
|
||||||
const methodChannel = MethodChannel('com.vodemn.lightmeter/keepScreenOn');
|
|
||||||
Future<Object?>? methodCallSuccessHandler(MethodCall methodCall) async {
|
Future<Object?>? methodCallSuccessHandler(MethodCall methodCall) async {
|
||||||
switch (methodCall.method) {
|
switch (methodCall.method) {
|
||||||
case "isKeepScreenOn":
|
case "isKeepScreenOn":
|
||||||
|
@ -21,45 +20,57 @@ void main() {
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
service = const CaffeineService();
|
service = const CaffeineService();
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
.setMockMethodCallHandler(methodChannel, methodCallSuccessHandler);
|
CaffeineService.caffeineMethodChannel,
|
||||||
|
methodCallSuccessHandler,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
.setMockMethodCallHandler(methodChannel, null);
|
CaffeineService.caffeineMethodChannel,
|
||||||
|
null,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
group(
|
group(
|
||||||
'isKeepScreenOn()',
|
'isKeepScreenOn()',
|
||||||
() {
|
() {
|
||||||
test('true', () async {
|
test('true', () async {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
.setMockMethodCallHandler(methodChannel, null);
|
CaffeineService.caffeineMethodChannel,
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
null,
|
||||||
.setMockMethodCallHandler(methodChannel, (methodCall) async {
|
);
|
||||||
switch (methodCall.method) {
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
case "isKeepScreenOn":
|
CaffeineService.caffeineMethodChannel,
|
||||||
return true;
|
(methodCall) async {
|
||||||
default:
|
switch (methodCall.method) {
|
||||||
return null;
|
case "isKeepScreenOn":
|
||||||
}
|
return true;
|
||||||
});
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
expectLater(service.isKeepScreenOn(), completion(true));
|
expectLater(service.isKeepScreenOn(), completion(true));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('false', () async {
|
test('false', () async {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
.setMockMethodCallHandler(methodChannel, null);
|
CaffeineService.caffeineMethodChannel,
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
null,
|
||||||
.setMockMethodCallHandler(methodChannel, (methodCall) async {
|
);
|
||||||
switch (methodCall.method) {
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
case "isKeepScreenOn":
|
CaffeineService.caffeineMethodChannel,
|
||||||
return false;
|
(methodCall) async {
|
||||||
default:
|
switch (methodCall.method) {
|
||||||
return null;
|
case "isKeepScreenOn":
|
||||||
}
|
return false;
|
||||||
});
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
expectLater(service.isKeepScreenOn(), completion(false));
|
expectLater(service.isKeepScreenOn(), completion(false));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,21 +11,24 @@ void main() {
|
||||||
{
|
{
|
||||||
'spotMetering': true,
|
'spotMetering': true,
|
||||||
'histogram': true,
|
'histogram': true,
|
||||||
|
'showFocalLength': true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
CameraFeature.spotMetering: true,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: true,
|
CameraFeature.histogram: true,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Legacy (no spotMetering & histogram)', () {
|
test('Legacy', () {
|
||||||
expect(
|
expect(
|
||||||
CameraFeaturesConfigJson.fromJson({}),
|
CameraFeaturesConfigJson.fromJson({}),
|
||||||
{
|
{
|
||||||
CameraFeature.spotMetering: false,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: false,
|
CameraFeature.histogram: false,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -37,10 +40,12 @@ void main() {
|
||||||
{
|
{
|
||||||
CameraFeature.spotMetering: true,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: true,
|
CameraFeature.histogram: true,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
}.toJson(),
|
}.toJson(),
|
||||||
{
|
{
|
||||||
'spotMetering': true,
|
'spotMetering': true,
|
||||||
'histogram': true,
|
'histogram': true,
|
||||||
|
'showFocalLength': true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -270,8 +270,9 @@ void main() {
|
||||||
expect(
|
expect(
|
||||||
service.cameraFeatures,
|
service.cameraFeatures,
|
||||||
{
|
{
|
||||||
CameraFeature.spotMetering: false,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: false,
|
CameraFeature.histogram: false,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -284,6 +285,7 @@ void main() {
|
||||||
{
|
{
|
||||||
CameraFeature.spotMetering: false,
|
CameraFeature.spotMetering: false,
|
||||||
CameraFeature.histogram: true,
|
CameraFeature.histogram: true,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -292,17 +294,18 @@ void main() {
|
||||||
when(
|
when(
|
||||||
() => sharedPreferences.setString(
|
() => sharedPreferences.setString(
|
||||||
UserPreferencesService.cameraFeaturesKey,
|
UserPreferencesService.cameraFeaturesKey,
|
||||||
"""{"spotMetering":false,"histogram":true}""",
|
"""{"spotMetering":false,"histogram":true,"showFocalLength":true}""",
|
||||||
),
|
),
|
||||||
).thenAnswer((_) => Future.value(true));
|
).thenAnswer((_) => Future.value(true));
|
||||||
service.cameraFeatures = {
|
service.cameraFeatures = {
|
||||||
CameraFeature.spotMetering: false,
|
CameraFeature.spotMetering: false,
|
||||||
CameraFeature.histogram: true,
|
CameraFeature.histogram: true,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
};
|
};
|
||||||
verify(
|
verify(
|
||||||
() => sharedPreferences.setString(
|
() => sharedPreferences.setString(
|
||||||
UserPreferencesService.cameraFeaturesKey,
|
UserPreferencesService.cameraFeaturesKey,
|
||||||
"""{"spotMetering":false,"histogram":true}""",
|
"""{"spotMetering":false,"histogram":true,"showFocalLength":true}""",
|
||||||
),
|
),
|
||||||
).called(1);
|
).called(1);
|
||||||
});
|
});
|
||||||
|
|
|
@ -27,14 +27,14 @@ void main() {
|
||||||
localPlatform = _MockLocalPlatform();
|
localPlatform = _MockLocalPlatform();
|
||||||
service = VolumeEventsService(localPlatform);
|
service = VolumeEventsService(localPlatform);
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
VolumeEventsService.volumeHandlingChannel,
|
VolumeEventsService.volumeMethodChannel,
|
||||||
methodCallSuccessHandler,
|
methodCallSuccessHandler,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
tearDown(() {
|
tearDown(() {
|
||||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler(
|
||||||
VolumeEventsService.volumeHandlingChannel,
|
VolumeEventsService.volumeMethodChannel,
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ void main() {
|
||||||
when(() => mockUserPreferencesService.cameraFeatures).thenReturn({
|
when(() => mockUserPreferencesService.cameraFeatures).thenReturn({
|
||||||
CameraFeature.spotMetering: true,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: true,
|
CameraFeature.histogram: true,
|
||||||
|
CameraFeature.showFocalLength: true,
|
||||||
});
|
});
|
||||||
when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en);
|
when(() => mockUserPreferencesService.locale).thenReturn(SupportedLocale.en);
|
||||||
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
when(() => mockUserPreferencesService.themeType).thenReturn(ThemeType.light);
|
||||||
|
@ -227,13 +228,6 @@ void main() {
|
||||||
expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2));
|
expect(find.text("${MeteringScreenLayoutFeature.equipmentProfiles}: true"), findsNWidgets(2));
|
||||||
expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: false"), findsNWidgets(2));
|
expect(find.text("${MeteringScreenLayoutFeature.extremeExposurePairs}: false"), findsNWidgets(2));
|
||||||
expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: false"), findsNWidgets(2));
|
expect(find.text("${MeteringScreenLayoutFeature.filmPicker}: false"), findsNWidgets(2));
|
||||||
verify(
|
|
||||||
() => mockUserPreferencesService.meteringScreenLayout = {
|
|
||||||
MeteringScreenLayoutFeature.extremeExposurePairs: false,
|
|
||||||
MeteringScreenLayoutFeature.filmPicker: false,
|
|
||||||
MeteringScreenLayoutFeature.equipmentProfiles: true,
|
|
||||||
},
|
|
||||||
).called(1);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -260,6 +254,7 @@ void main() {
|
||||||
onPressed: () => UserPreferencesProvider.of(context).setCameraFeature({
|
onPressed: () => UserPreferencesProvider.of(context).setCameraFeature({
|
||||||
CameraFeature.spotMetering: true,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: false,
|
CameraFeature.histogram: false,
|
||||||
|
CameraFeature.showFocalLength: false,
|
||||||
}),
|
}),
|
||||||
child: const Text(''),
|
child: const Text(''),
|
||||||
),
|
),
|
||||||
|
@ -270,15 +265,18 @@ void main() {
|
||||||
// Match `findsNWidgets(2)` to verify that `cameraFeatureOf` specific results are the same as the whole config
|
// Match `findsNWidgets(2)` to verify that `cameraFeatureOf` specific results are the same as the whole config
|
||||||
expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2));
|
expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2));
|
||||||
expect(find.text("${CameraFeature.histogram}: true"), findsNWidgets(2));
|
expect(find.text("${CameraFeature.histogram}: true"), findsNWidgets(2));
|
||||||
|
expect(find.text("${CameraFeature.showFocalLength}: true"), findsNWidgets(2));
|
||||||
|
|
||||||
await tester.tap(find.byType(ElevatedButton));
|
await tester.tap(find.byType(ElevatedButton));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2));
|
expect(find.text("${CameraFeature.spotMetering}: true"), findsNWidgets(2));
|
||||||
expect(find.text("${CameraFeature.histogram}: false"), findsNWidgets(2));
|
expect(find.text("${CameraFeature.histogram}: false"), findsNWidgets(2));
|
||||||
|
expect(find.text("${CameraFeature.showFocalLength}: false"), findsNWidgets(2));
|
||||||
verify(
|
verify(
|
||||||
() => mockUserPreferencesService.cameraFeatures = {
|
() => mockUserPreferencesService.cameraFeatures = {
|
||||||
CameraFeature.spotMetering: true,
|
CameraFeature.spotMetering: true,
|
||||||
CameraFeature.histogram: false,
|
CameraFeature.histogram: false,
|
||||||
|
CameraFeature.showFocalLength: false,
|
||||||
},
|
},
|
||||||
).called(1);
|
).called(1);
|
||||||
},
|
},
|
||||||
|
|
Before Width: | Height: | Size: 301 KiB After Width: | Height: | Size: 321 KiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
|
@ -1,24 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lightmeter/utils/ev_from_bytes.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('evFromImage', () {
|
|
||||||
test(
|
|
||||||
'camera_stub_image.jpg',
|
|
||||||
() {
|
|
||||||
final bytes = File('assets/camera_stub_image.jpg').readAsBytesSync();
|
|
||||||
expectLater(evFromImage(bytes), completion(8.25230310752341));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test(
|
|
||||||
'no EXIF',
|
|
||||||
() {
|
|
||||||
final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync();
|
|
||||||
expectLater(evFromImage(bytes), throwsArgumentError);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
27
test/utils/exif_utils_test.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:exif/exif.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:lightmeter/utils/exif_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('evFromTags', () {
|
||||||
|
test(
|
||||||
|
'camera_stub_image.jpg',
|
||||||
|
() async {
|
||||||
|
final bytes = File('assets/camera_stub_image.jpg').readAsBytesSync();
|
||||||
|
final tags = await readExifFromBytes(bytes);
|
||||||
|
expect(evFromTags(tags), 8.25230310752341);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test(
|
||||||
|
'no EXIF',
|
||||||
|
() async {
|
||||||
|
final bytes = File('assets/launcher_icon_dev_512.png').readAsBytesSync();
|
||||||
|
final tags = await readExifFromBytes(bytes);
|
||||||
|
expect(() => evFromTags(tags), throwsArgumentError);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|