split other platform handlers to separate files

This commit is contained in:
Vadim 2025-05-11 12:48:10 +02:00
parent 56ac6fe558
commit 4c689db8ee
12 changed files with 226 additions and 147 deletions

View file

@ -2,23 +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.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 lateinit var volumeEventChannel: EventChannel
private var volumeEventsEmitter: EventSink? = null
private var handleVolume = false
private val cameraInfoPlatformChannel = CameraInfoPlatformChannel() private val cameraInfoPlatformChannel = CameraInfoPlatformChannel()
private val volumePlatformChannel = VolumePlatformChannel()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -27,75 +21,24 @@ class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine) super.configureFlutterEngine(flutterEngine)
cameraInfoPlatformChannel.onAttachedToEngine(flutterEngine.dartExecutor.binaryMessenger, context) val binaryMessenger = flutterEngine.dartExecutor.binaryMessenger
caffeinePlatformChannel.onAttachedToEngine(binaryMessenger, window)
keepScreenOnChannel = MethodChannel( cameraInfoPlatformChannel.onAttachedToEngine(binaryMessenger, context)
flutterEngine.dartExecutor.binaryMessenger, volumePlatformChannel.onAttachedToEngine(binaryMessenger)
"com.vodemn.lightmeter/keepScreenOn"
)
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)
volumeEventChannel.setStreamHandler(null)
cameraInfoPlatformChannel.onDestroy() cameraInfoPlatformChannel.onDestroy()
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)
} }
} }
} }

View file

@ -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)
}
}

View file

@ -5,30 +5,31 @@ import android.hardware.camera2.CameraCharacteristics
import android.hardware.camera2.CameraManager import android.hardware.camera2.CameraManager
import android.hardware.camera2.CameraMetadata.LENS_FACING_BACK import android.hardware.camera2.CameraMetadata.LENS_FACING_BACK
import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.sqrt import kotlin.math.sqrt
/** CameraInfoPlatformChannel */ /** CameraInfoPlatformChannel */
class CameraInfoPlatformChannel : MethodChannel.MethodCallHandler { class CameraInfoPlatformChannel {
private lateinit var channel: MethodChannel private lateinit var channel: MethodChannel
private lateinit var cameraManager: CameraManager private lateinit var cameraManager: CameraManager
private var mainCameraEfl: Double? = null private var mainCameraEfl: Double? = null
fun onAttachedToEngine(binaryMessenger: BinaryMessenger, context: Context) { fun onAttachedToEngine(binaryMessenger: BinaryMessenger, context: Context) {
channel = MethodChannel(binaryMessenger, "com.vodemn.lightmeter.CameraInfoPlatformChannel") channel = MethodChannel(
binaryMessenger,
"com.vodemn.lightmeter.CameraInfoPlatformChannel.MethodChannel"
)
cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager cameraManager = context.getSystemService(Context.CAMERA_SERVICE) as CameraManager
channel.setMethodCallHandler(this) channel.setMethodCallHandler { call, result ->
} when (call.method) {
"mainCameraEfl" -> {
mainCameraEfl = mainCameraEfl ?: getMainCameraFocalLength35mm()
result.success(mainCameraEfl)
}
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { else -> result.notImplemented()
when (call.method) {
"mainCameraEfl" -> {
mainCameraEfl = mainCameraEfl ?: getMainCameraFocalLength35mm()
result.success(mainCameraEfl)
} }
else -> result.notImplemented()
} }
} }
@ -45,7 +46,8 @@ class CameraInfoPlatformChannel : MethodChannel.MethodCallHandler {
} }
private fun CameraCharacteristics.focalLength35mm(): Double? { private fun CameraCharacteristics.focalLength35mm(): Double? {
val defaultFocalLength = get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.first() val defaultFocalLength =
get(CameraCharacteristics.LENS_INFO_AVAILABLE_FOCAL_LENGTHS)?.first()
val sensorSize = get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE) val sensorSize = get(CameraCharacteristics.SENSOR_INFO_PHYSICAL_SIZE)
return if (defaultFocalLength != null && sensorSize != null) { return if (defaultFocalLength != null && sensorSize != null) {
// https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length#Conversions // https://en.wikipedia.org/wiki/35_mm_equivalent_focal_length#Conversions

View file

@ -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
}
}
}

View file

@ -8,25 +8,7 @@ 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)
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)
} }

View file

@ -0,0 +1,36 @@
//
// 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)
}
})
}
}

View file

@ -100,13 +100,11 @@ 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;
}); });
} }

View file

@ -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!);
} }
} }

View file

@ -5,7 +5,9 @@ import 'package:flutter/services.dart';
class CameraInfoService { class CameraInfoService {
@visibleForTesting @visibleForTesting
static const cameraInfoPlatformChannel = MethodChannel("com.vodemn.lightmeter.CameraInfoPlatformChannel"); static const cameraInfoPlatformChannel = MethodChannel(
"com.vodemn.lightmeter.CameraInfoPlatformChannel.MethodChannel",
);
const CameraInfoService(); const CameraInfoService();

View file

@ -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);
} }
} }

View file

@ -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));
}); });
}, },

View file

@ -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,
); );
}); });