From 10ecc363f6b47676df4015b8656ab165f9ef99cd Mon Sep 17 00:00:00 2001 From: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Date: Sun, 1 Mar 2026 10:16:23 +0530 Subject: [PATCH] feat: show notification and battery optimization warning --- i18n/en.json | 2 + .../immich/permission/PermissionApi.g.kt | 45 +++++- .../immich/permission/PermissionApiImpl.kt | 13 ++ .../Runner/Permission/PermissionApi.g.swift | 82 +++++++++- .../Runner/Permission/PermissionApiImpl.swift | 4 + .../lib/pages/backup/drift_backup.page.dart | 143 +++++++++++++++++- mobile/lib/platform/permission_api.g.dart | 27 ++++ .../providers/app_life_cycle.provider.dart | 2 +- ...provider.dart => permission.provider.dart} | 32 ++++ .../settings/notification_setting.dart | 2 +- mobile/mise.toml | 22 +-- mobile/pigeon/permission_api.dart | 6 +- 12 files changed, 355 insertions(+), 25 deletions(-) rename mobile/lib/providers/{notification_permission.provider.dart => permission.provider.dart} (54%) diff --git a/i18n/en.json b/i18n/en.json index f4ad3001c2..adf73aac01 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -699,6 +699,7 @@ "backup_settings_subtitle": "Manage upload settings", "backup_upload_details_page_more_details": "Tap for more details", "backward": "Backward", + "battery_optimization_backup_reliability": "Disabling battery optimizations can improve the reliability of background backup", "biometric_auth_enabled": "Biometric authentication enabled", "biometric_locked_out": "You are locked out of biometric authentication", "biometric_no_options": "No biometric options available", @@ -1689,6 +1690,7 @@ "not_selected": "Not selected", "notes": "Notes", "nothing_here_yet": "Nothing here yet", + "notification_backup_reliability": "Enable notifications to improve background backup reliability", "notification_permission_dialog_content": "To enable notifications, go to Settings and select allow.", "notification_permission_list_tile_content": "Grant permission to enable notifications.", "notification_permission_list_tile_enable_button": "Enable Notifications", diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt index 48a1a72037..5f7bf806b4 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt @@ -47,18 +47,44 @@ class FlutterError ( override val message: String? = null, val details: Any? = null ) : RuntimeException() + +enum class PermissionStatus(val raw: Int) { + GRANTED(0), + DENIED(1), + PERMANENTLY_DENIED(2); + + companion object { + fun ofRaw(raw: Int): PermissionStatus? { + return values().firstOrNull { it.raw == raw } + } + } +} private open class PermissionApiPigeonCodec : StandardMessageCodec() { override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? { - return super.readValueOfType(type, buffer) + return when (type) { + 129.toByte() -> { + return (readValue(buffer) as Long?)?.let { + PermissionStatus.ofRaw(it.toInt()) + } + } + else -> super.readValueOfType(type, buffer) + } } override fun writeValue(stream: ByteArrayOutputStream, value: Any?) { - super.writeValue(stream, value) + when (value) { + is PermissionStatus -> { + stream.write(129) + writeValue(stream, value.raw.toLong()) + } + else -> super.writeValue(stream, value) + } } } /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ interface PermissionApi { + fun isIgnoringBatteryOptimizations(): PermissionStatus fun hasManageMediaPermission(): Boolean fun requestManageMediaPermission(callback: (Result) -> Unit) fun manageMediaPermission(callback: (Result) -> Unit) @@ -72,6 +98,21 @@ interface PermissionApi { @JvmOverloads fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else "" + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$separatedMessageChannelSuffix", codec) + if (api != null) { + channel.setMessageHandler { _, reply -> + val wrapped: List = try { + listOf(api.isIgnoringBatteryOptimizations()) + } catch (exception: Throwable) { + PermissionApiPigeonUtils.wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } run { val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$separatedMessageChannelSuffix", codec) if (api != null) { diff --git a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt index c3443bb06d..4e4ce7b424 100644 --- a/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt +++ b/mobile/android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApiImpl.kt @@ -1,13 +1,26 @@ package app.alextran.immich.permission import android.content.Context +import android.os.PowerManager import app.alextran.immich.core.ImmichPlugin import io.flutter.embedding.engine.plugins.activity.ActivityAware import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding class PermissionApiImpl(context: Context) : ImmichPlugin(), PermissionApi, ActivityAware { + private val ctx: Context = context.applicationContext private val manageMediaPermissionDelegate = ManageMediaPermissionDelegate(context) + private val powerManager = + ctx.getSystemService(Context.POWER_SERVICE) as PowerManager + + + override fun isIgnoringBatteryOptimizations(): PermissionStatus { + if (powerManager.isIgnoringBatteryOptimizations(ctx.packageName)) { + return PermissionStatus.GRANTED + } + return PermissionStatus.DENIED + } + override fun hasManageMediaPermission(): Boolean = manageMediaPermissionDelegate.hasManageMediaPermission() diff --git a/mobile/ios/Runner/Permission/PermissionApi.g.swift b/mobile/ios/Runner/Permission/PermissionApi.g.swift index 53ad9e5b11..988e9b56dd 100644 --- a/mobile/ios/Runner/Permission/PermissionApi.g.swift +++ b/mobile/ios/Runner/Permission/PermissionApi.g.swift @@ -11,6 +11,24 @@ import Foundation #error("Unsupported platform.") #endif +/// Error class for passing custom error details to Dart side. +final class PigeonError: Error { + let code: String + let message: String? + let details: Sendable? + + init(code: String, message: String?, details: Sendable?) { + self.code = code + self.message = message + self.details = details + } + + var localizedDescription: String { + return + "PigeonError(code: \(code), message: \(message ?? ""), details: \(details ?? "")" + } +} + private func wrapResult(_ result: Any?) -> [Any?] { return [result] } @@ -46,8 +64,57 @@ private func nilOrValue(_ value: Any?) -> T? { return value as! T? } + +enum PermissionStatus: Int { + case granted = 0 + case denied = 1 + case permanentlyDenied = 2 +} + +private class PermissionApiPigeonCodecReader: FlutterStandardReader { + override func readValue(ofType type: UInt8) -> Any? { + switch type { + case 129: + let enumResultAsInt: Int? = nilOrValue(self.readValue() as! Int?) + if let enumResultAsInt = enumResultAsInt { + return PermissionStatus(rawValue: enumResultAsInt) + } + return nil + default: + return super.readValue(ofType: type) + } + } +} + +private class PermissionApiPigeonCodecWriter: FlutterStandardWriter { + override func writeValue(_ value: Any) { + if let value = value as? PermissionStatus { + super.writeByte(129) + super.writeValue(value.rawValue) + } else { + super.writeValue(value) + } + } +} + +private class PermissionApiPigeonCodecReaderWriter: FlutterStandardReaderWriter { + override func reader(with data: Data) -> FlutterStandardReader { + return PermissionApiPigeonCodecReader(data: data) + } + + override func writer(with data: NSMutableData) -> FlutterStandardWriter { + return PermissionApiPigeonCodecWriter(data: data) + } +} + +class PermissionApiPigeonCodec: FlutterStandardMessageCodec, @unchecked Sendable { + static let shared = PermissionApiPigeonCodec(readerWriter: PermissionApiPigeonCodecReaderWriter()) +} + + /// Generated protocol from Pigeon that represents a handler of messages from Flutter. protocol PermissionApi { + func isIgnoringBatteryOptimizations() throws -> PermissionStatus func hasManageMediaPermission() throws -> Bool func requestManageMediaPermission(completion: @escaping (Result) -> Void) func manageMediaPermission(completion: @escaping (Result) -> Void) @@ -55,10 +122,23 @@ protocol PermissionApi { /// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. class PermissionApiSetup { - static var codec: FlutterStandardMessageCodec { FlutterStandardMessageCodec.sharedInstance() } + static var codec: FlutterStandardMessageCodec { PermissionApiPigeonCodec.shared } /// Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. static func setUp(binaryMessenger: FlutterBinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") { let channelSuffix = messageChannelSuffix.count > 0 ? ".\(messageChannelSuffix)" : "" + let isIgnoringBatteryOptimizationsChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) + if let api = api { + isIgnoringBatteryOptimizationsChannel.setMessageHandler { _, reply in + do { + let result = try api.isIgnoringBatteryOptimizations() + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + isIgnoringBatteryOptimizationsChannel.setMessageHandler(nil) + } let hasManageMediaPermissionChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission\(channelSuffix)", binaryMessenger: binaryMessenger, codec: codec) if let api = api { hasManageMediaPermissionChannel.setMessageHandler { _, reply in diff --git a/mobile/ios/Runner/Permission/PermissionApiImpl.swift b/mobile/ios/Runner/Permission/PermissionApiImpl.swift index e725b742fd..3d8e89486d 100644 --- a/mobile/ios/Runner/Permission/PermissionApiImpl.swift +++ b/mobile/ios/Runner/Permission/PermissionApiImpl.swift @@ -1,6 +1,10 @@ import Foundation class PermissionApiImpl: PermissionApi { + func isIgnoringBatteryOptimizations() throws -> PermissionStatus { + return PermissionStatus.granted; + } + func hasManageMediaPermission() throws -> Bool { return false } diff --git a/mobile/lib/pages/backup/drift_backup.page.dart b/mobile/lib/pages/backup/drift_backup.page.dart index 2e18c3edc6..9e78fb4795 100644 --- a/mobile/lib/pages/backup/drift_backup.page.dart +++ b/mobile/lib/pages/backup/drift_backup.page.dart @@ -8,6 +8,7 @@ import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; +import 'package:immich_mobile/extensions/platform_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/generated/translations.g.dart'; @@ -15,11 +16,16 @@ import 'package:immich_mobile/presentation/widgets/backup/backup_toggle_button.w import 'package:immich_mobile/providers/background_sync.provider.dart'; import 'package:immich_mobile/providers/backup/backup_album.provider.dart'; import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; +import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/sync_status.provider.dart'; import 'package:immich_mobile/providers/user.provider.dart'; import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/backup/backup_info_card.dart'; +import 'package:immich_ui/immich_ui.dart'; import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'package:wakelock_plus/wakelock_plus.dart'; @RoutePage() @@ -162,11 +168,7 @@ class _DriftBackupPageState extends ConsumerState { ), ), }, - TextButton.icon( - icon: const Icon(Icons.info_outline_rounded), - onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), - label: Text("view_details".t(context: context)), - ), + const _BackupFooter(), ], ], ), @@ -177,6 +179,137 @@ class _DriftBackupPageState extends ConsumerState { } } +class _BackupFooter extends ConsumerStatefulWidget { + const _BackupFooter(); + + @override + ConsumerState<_BackupFooter> createState() => _BackupFooterState(); +} + +class _BackupFooterState extends ConsumerState<_BackupFooter> with WidgetsBindingObserver { + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (CurrentPlatform.isAndroid && state == AppLifecycleState.resumed && mounted) { + unawaited(ref.read(notificationPermissionProvider.notifier).getNotificationPermission()); + unawaited(ref.read(batteryOptimizationProvider.notifier).getBatteryOptimizationPermission()); + } + } + + void showPermissionsDialog() { + showDialog( + context: context, + builder: (ctx) => AlertDialog( + content: Text(context.t.notification_permission_dialog_content), + actions: [ + ImmichTextButton( + labelText: context.t.cancel, + variant: .ghost, + expanded: false, + onPressed: () => ContextHelper(ctx).pop(), + ), + ImmichTextButton( + labelText: context.t.settings, + variant: .ghost, + expanded: false, + onPressed: () { + ContextHelper(context).pop(); + openAppSettings(); + }, + ), + ], + ), + ); + } + + void showBatteryOptimizationInfo() { + showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext ctx) { + return AlertDialog( + title: Text(context.t.backup_controller_page_background_battery_info_title), + content: SingleChildScrollView(child: Text(context.t.backup_controller_page_background_battery_info_message)), + actions: [ + ImmichTextButton( + labelText: context.t.backup_controller_page_background_battery_info_link, + variant: .ghost, + expanded: false, + onPressed: () => launchUrl(Uri.parse('https://dontkillmyapp.com'), mode: LaunchMode.externalApplication), + ), + ImmichTextButton( + labelText: context.t.backup_controller_page_background_battery_info_ok, + variant: .ghost, + expanded: false, + onPressed: () => ContextHelper(ctx).pop(), + ), + ], + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final isBackupEnabled = ref.watch(appConfigProvider.select((config) => config.backup.enabled)); + final notificationStatus = ref.watch(notificationPermissionProvider); + final batteryOptimizationStatus = ref.watch(batteryOptimizationProvider).valueOrNull; + + return Column( + children: [ + if (CurrentPlatform.isAndroid && isBackupEnabled) ...[ + if (notificationStatus != PermissionStatus.granted) + TextButton.icon( + iconAlignment: .end, + icon: Icon(Icons.open_in_new_outlined, color: context.colorScheme.onSurfaceSecondary), + label: Text( + context.t.notification_backup_reliability, + textAlign: TextAlign.left, + style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ), + onPressed: () { + ref.read(notificationPermissionProvider.notifier).requestNotificationPermission().then((p) { + if (p == PermissionStatus.permanentlyDenied) { + showPermissionsDialog(); + } + }); + }, + ), + if (notificationStatus != PermissionStatus.granted && batteryOptimizationStatus != PermissionStatus.granted) + const Divider(indent: 32, endIndent: 32), + if (batteryOptimizationStatus != PermissionStatus.granted) + TextButton.icon( + iconAlignment: .end, + icon: Icon(Icons.open_in_new_outlined, color: context.colorScheme.onSurfaceSecondary), + label: Text( + context.t.battery_optimization_backup_reliability, + textAlign: TextAlign.left, + style: context.textTheme.bodySmall?.copyWith(color: context.colorScheme.onSurfaceSecondary), + ), + onPressed: showBatteryOptimizationInfo, + ), + ], + TextButton.icon( + icon: const Icon(Icons.info_outline_rounded), + onPressed: () => context.pushRoute(const DriftUploadDetailRoute()), + label: Text(context.t.view_details), + ), + ], + ); + } +} + class _BackupAlbumSelectionCard extends ConsumerWidget { const _BackupAlbumSelectionCard(); diff --git a/mobile/lib/platform/permission_api.g.dart b/mobile/lib/platform/permission_api.g.dart index d2646e482f..7b85d611d2 100644 --- a/mobile/lib/platform/permission_api.g.dart +++ b/mobile/lib/platform/permission_api.g.dart @@ -26,6 +26,8 @@ Object? _extractReplyValueOrThrow(List? replyList, String channelName, return replyList.firstOrNull; } +enum PermissionStatus { granted, denied, permanentlyDenied } + class _PigeonCodec extends StandardMessageCodec { const _PigeonCodec(); @override @@ -33,6 +35,9 @@ class _PigeonCodec extends StandardMessageCodec { if (value is int) { buffer.putUint8(4); buffer.putInt64(value); + } else if (value is PermissionStatus) { + buffer.putUint8(129); + writeValue(buffer, value.index); } else { super.writeValue(buffer, value); } @@ -41,6 +46,9 @@ class _PigeonCodec extends StandardMessageCodec { @override Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { + case 129: + final value = readValue(buffer) as int?; + return value == null ? null : PermissionStatus.values[value]; default: return super.readValueOfType(type, buffer); } @@ -60,6 +68,25 @@ class PermissionApi { final String pigeonVar_messageChannelSuffix; + Future isIgnoringBatteryOptimizations() async { + final pigeonVar_channelName = + 'dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$pigeonVar_messageChannelSuffix'; + final pigeonVar_channel = BasicMessageChannel( + pigeonVar_channelName, + pigeonChannelCodec, + binaryMessenger: pigeonVar_binaryMessenger, + ); + final Future pigeonVar_sendFuture = pigeonVar_channel.send(null); + final pigeonVar_replyList = await pigeonVar_sendFuture as List?; + + final Object? pigeonVar_replyValue = _extractReplyValueOrThrow( + pigeonVar_replyList, + pigeonVar_channelName, + isNullValid: false, + ); + return pigeonVar_replyValue! as PermissionStatus; + } + Future hasManageMediaPermission() async { final pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.PermissionApi.hasManageMediaPermission$pigeonVar_messageChannelSuffix'; diff --git a/mobile/lib/providers/app_life_cycle.provider.dart b/mobile/lib/providers/app_life_cycle.provider.dart index 5cd294d781..bb1877cbd7 100644 --- a/mobile/lib/providers/app_life_cycle.provider.dart +++ b/mobile/lib/providers/app_life_cycle.provider.dart @@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/backup/drift_backup.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:immich_mobile/providers/infrastructure/settings.provider.dart'; -import 'package:immich_mobile/providers/notification_permission.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/providers/server_info.provider.dart'; import 'package:immich_mobile/providers/websocket.provider.dart'; import 'package:logging/logging.dart'; diff --git a/mobile/lib/providers/notification_permission.provider.dart b/mobile/lib/providers/permission.provider.dart similarity index 54% rename from mobile/lib/providers/notification_permission.provider.dart rename to mobile/lib/providers/permission.provider.dart index da0badd4ec..ffae87e679 100644 --- a/mobile/lib/providers/notification_permission.provider.dart +++ b/mobile/lib/providers/permission.provider.dart @@ -1,6 +1,9 @@ +import 'dart:async'; import 'dart:io'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/platform/permission_api.g.dart' as pm; +import 'package:immich_mobile/providers/infrastructure/platform.provider.dart'; import 'package:permission_handler/permission_handler.dart'; class NotificationPermissionNotifier extends StateNotifier { @@ -39,3 +42,32 @@ class NotificationPermissionNotifier extends StateNotifier { final notificationPermissionProvider = StateNotifierProvider( (ref) => NotificationPermissionNotifier(), ); + +final batteryOptimizationProvider = AsyncNotifierProvider( + BatteryOptimizationNotifier.new, +); + +class BatteryOptimizationNotifier extends AsyncNotifier { + Future getBatteryOptimizationPermission() async { + final PermissionStatus status; + final isIgnoring = await ref.read(permissionApiProvider).isIgnoringBatteryOptimizations().then((p) => p.toStatus()); + if (isIgnoring == PermissionStatus.granted) { + status = PermissionStatus.granted; + } else { + status = PermissionStatus.denied; + } + state = AsyncValue.data(status); + return status; + } + + @override + FutureOr build() => getBatteryOptimizationPermission(); +} + +extension on pm.PermissionStatus { + PermissionStatus toStatus() => switch (this) { + pm.PermissionStatus.granted => PermissionStatus.granted, + pm.PermissionStatus.denied => PermissionStatus.denied, + pm.PermissionStatus.permanentlyDenied => PermissionStatus.permanentlyDenied, + }; +} diff --git a/mobile/lib/widgets/settings/notification_setting.dart b/mobile/lib/widgets/settings/notification_setting.dart index 46120bb218..cbef5ea109 100644 --- a/mobile/lib/widgets/settings/notification_setting.dart +++ b/mobile/lib/widgets/settings/notification_setting.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; -import 'package:immich_mobile/providers/notification_permission.provider.dart'; +import 'package:immich_mobile/providers/permission.provider.dart'; import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart'; import 'package:immich_mobile/widgets/settings/settings_sub_page_scaffold.dart'; import 'package:permission_handler/permission_handler.dart'; diff --git a/mobile/mise.toml b/mobile/mise.toml index a0f25718ac..cc02e5e782 100644 --- a/mobile/mise.toml +++ b/mobile/mise.toml @@ -22,14 +22,8 @@ run = "dart run build_runner watch --delete-conflicting-outputs" alias = "pigeon" description = "Generate pigeon platform code" run = [ - "dart run pigeon --input pigeon/native_sync_api.dart", - "dart run pigeon --input pigeon/local_image_api.dart", - "dart run pigeon --input pigeon/remote_image_api.dart", - "dart run pigeon --input pigeon/background_worker_api.dart", - "dart run pigeon --input pigeon/background_worker_lock_api.dart", - "dart run pigeon --input pigeon/connectivity_api.dart", - "dart run pigeon --input pigeon/network_api.dart", - "dart format lib/platform/native_sync_api.g.dart lib/platform/local_image_api.g.dart lib/platform/remote_image_api.g.dart lib/platform/background_worker_api.g.dart lib/platform/background_worker_lock_api.g.dart lib/platform/connectivity_api.g.dart lib/platform/network_api.g.dart", + "ls pigeon/*.dart | xargs -n1 -P4 -I{} dart run pigeon --input {}", + "dart format lib/platform/", ] [tasks."codegen:translation"] @@ -132,10 +126,10 @@ run = "dcm fix lib" [tasks.checklist] run = [ - {task = "codegen:pigeon" }, - {task = "codegen:dart" }, - {task = "codegen:translation" }, - {task = "analyze" }, - {task = "format" }, - {task = "test" }, + { task = "codegen:pigeon" }, + { task = "codegen:dart" }, + { task = "codegen:translation" }, + { task = "analyze" }, + { task = "format" }, + { task = "test" }, ] diff --git a/mobile/pigeon/permission_api.dart b/mobile/pigeon/permission_api.dart index a924f32e27..42099d4e7c 100644 --- a/mobile/pigeon/permission_api.dart +++ b/mobile/pigeon/permission_api.dart @@ -1,10 +1,12 @@ import 'package:pigeon/pigeon.dart'; +enum PermissionStatus { granted, denied, permanentlyDenied } + @ConfigurePigeon( PigeonOptions( dartOut: 'lib/platform/permission_api.g.dart', swiftOut: 'ios/Runner/Permission/PermissionApi.g.swift', - swiftOptions: SwiftOptions(), + swiftOptions: SwiftOptions(includeErrorClass: false), kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt', kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'), dartOptions: DartOptions(), @@ -13,6 +15,8 @@ import 'package:pigeon/pigeon.dart'; ) @HostApi() abstract class PermissionApi { + PermissionStatus isIgnoringBatteryOptimizations(); + bool hasManageMediaPermission(); @async