mirror of
https://github.com/immich-app/immich.git
synced 2026-05-17 21:12:15 -04:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0c9263b69f |
@@ -693,6 +693,7 @@
|
|||||||
"backup_settings_subtitle": "Manage upload settings",
|
"backup_settings_subtitle": "Manage upload settings",
|
||||||
"backup_upload_details_page_more_details": "Tap for more details",
|
"backup_upload_details_page_more_details": "Tap for more details",
|
||||||
"backward": "Backward",
|
"backward": "Backward",
|
||||||
|
"battery_optimization_backup_reliability": "Disabling battery optimizations can improve the reliability of background backup",
|
||||||
"biometric_auth_enabled": "Biometric authentication enabled",
|
"biometric_auth_enabled": "Biometric authentication enabled",
|
||||||
"biometric_locked_out": "You are locked out of biometric authentication",
|
"biometric_locked_out": "You are locked out of biometric authentication",
|
||||||
"biometric_no_options": "No biometric options available",
|
"biometric_no_options": "No biometric options available",
|
||||||
@@ -1667,6 +1668,7 @@
|
|||||||
"not_selected": "Not selected",
|
"not_selected": "Not selected",
|
||||||
"notes": "Notes",
|
"notes": "Notes",
|
||||||
"nothing_here_yet": "Nothing here yet",
|
"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_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_content": "Grant permission to enable notifications.",
|
||||||
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
"notification_permission_list_tile_enable_button": "Enable Notifications",
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import app.alextran.immich.images.LocalImageApi
|
|||||||
import app.alextran.immich.images.LocalImagesImpl
|
import app.alextran.immich.images.LocalImagesImpl
|
||||||
import app.alextran.immich.images.RemoteImageApi
|
import app.alextran.immich.images.RemoteImageApi
|
||||||
import app.alextran.immich.images.RemoteImagesImpl
|
import app.alextran.immich.images.RemoteImagesImpl
|
||||||
|
import app.alextran.immich.permission.PermissionApi
|
||||||
|
import app.alextran.immich.permission.PermissionApiImpl
|
||||||
import app.alextran.immich.sync.NativeSyncApi
|
import app.alextran.immich.sync.NativeSyncApi
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl26
|
import app.alextran.immich.sync.NativeSyncApiImpl26
|
||||||
import app.alextran.immich.sync.NativeSyncApiImpl30
|
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||||
@@ -50,6 +52,7 @@ class MainActivity : FlutterFragmentActivity() {
|
|||||||
|
|
||||||
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
||||||
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
||||||
|
PermissionApi.setUp(messenger, PermissionApiImpl(ctx))
|
||||||
|
|
||||||
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
flutterEngine.plugins.add(backgroundEngineLockImpl)
|
||||||
flutterEngine.plugins.add(nativeSyncApiImpl)
|
flutterEngine.plugins.add(nativeSyncApiImpl)
|
||||||
|
|||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||||
|
|
||||||
|
package app.alextran.immich.permission
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import io.flutter.plugin.common.BasicMessageChannel
|
||||||
|
import io.flutter.plugin.common.BinaryMessenger
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MessageCodec
|
||||||
|
import io.flutter.plugin.common.StandardMethodCodec
|
||||||
|
import io.flutter.plugin.common.StandardMessageCodec
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
private object PermissionApiPigeonUtils {
|
||||||
|
|
||||||
|
fun wrapResult(result: Any?): List<Any?> {
|
||||||
|
return listOf(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun wrapError(exception: Throwable): List<Any?> {
|
||||||
|
return if (exception is FlutterError) {
|
||||||
|
listOf(
|
||||||
|
exception.code,
|
||||||
|
exception.message,
|
||||||
|
exception.details
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
listOf(
|
||||||
|
exception.javaClass.simpleName,
|
||||||
|
exception.toString(),
|
||||||
|
"Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error class for passing custom error details to Flutter via a thrown PlatformException.
|
||||||
|
* @property code The error code.
|
||||||
|
* @property message The error message.
|
||||||
|
* @property details The error details. Must be a datatype supported by the api codec.
|
||||||
|
*/
|
||||||
|
class FlutterError (
|
||||||
|
val code: String,
|
||||||
|
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 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?) {
|
||||||
|
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
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/** The codec used by PermissionApi. */
|
||||||
|
val codec: MessageCodec<Any?> by lazy {
|
||||||
|
PermissionApiPigeonCodec()
|
||||||
|
}
|
||||||
|
/** Sets up an instance of `PermissionApi` to handle messages through the `binaryMessenger`. */
|
||||||
|
@JvmOverloads
|
||||||
|
fun setUp(binaryMessenger: BinaryMessenger, api: PermissionApi?, messageChannelSuffix: String = "") {
|
||||||
|
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||||
|
run {
|
||||||
|
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$separatedMessageChannelSuffix", codec)
|
||||||
|
if (api != null) {
|
||||||
|
channel.setMessageHandler { _, reply ->
|
||||||
|
val wrapped: List<Any?> = try {
|
||||||
|
listOf(api.isIgnoringBatteryOptimizations())
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
PermissionApiPigeonUtils.wrapError(exception)
|
||||||
|
}
|
||||||
|
reply.reply(wrapped)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
channel.setMessageHandler(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
package app.alextran.immich.permission
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.PowerManager
|
||||||
|
|
||||||
|
class PermissionApiImpl(context: Context) : PermissionApi {
|
||||||
|
private val ctx: Context = context.applicationContext
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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/domain/models/store.model.dart';
|
||||||
import 'package:immich_mobile/entities/store.entity.dart';
|
import 'package:immich_mobile/entities/store.entity.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.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/theme_extensions.dart';
|
||||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||||
import 'package:immich_mobile/generated/translations.g.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/background_sync.provider.dart';
|
||||||
import 'package:immich_mobile/providers/backup/backup_album.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/backup/drift_backup.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/infrastructure/store.provider.dart';
|
||||||
|
import 'package:immich_mobile/providers/permission.provider.dart';
|
||||||
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
import 'package:immich_mobile/providers/sync_status.provider.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.dart';
|
import 'package:immich_mobile/providers/user.provider.dart';
|
||||||
import 'package:immich_mobile/routing/router.dart';
|
import 'package:immich_mobile/routing/router.dart';
|
||||||
import 'package:immich_mobile/widgets/backup/backup_info_card.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:logging/logging.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
|
||||||
@RoutePage()
|
@RoutePage()
|
||||||
@@ -162,11 +168,7 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
TextButton.icon(
|
const _BackupFooter(),
|
||||||
icon: const Icon(Icons.info_outline_rounded),
|
|
||||||
onPressed: () => context.pushRoute(const DriftUploadDetailRoute()),
|
|
||||||
label: Text("view_details".t(context: context)),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -177,6 +179,137 @@ class _DriftBackupPageState extends ConsumerState<DriftBackupPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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<void>(
|
||||||
|
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(_backupStatusProvider).valueOrNull ?? false;
|
||||||
|
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 {
|
class _BackupAlbumSelectionCard extends ConsumerWidget {
|
||||||
const _BackupAlbumSelectionCard();
|
const _BackupAlbumSelectionCard();
|
||||||
|
|
||||||
@@ -527,3 +660,7 @@ class _PreparingStatusState extends ConsumerState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final _backupStatusProvider = StreamProvider.autoDispose<bool?>((ref) async* {
|
||||||
|
yield* ref.watch(storeServiceProvider).watch(StoreKey.enableBackup);
|
||||||
|
});
|
||||||
|
|||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
// Autogenerated from Pigeon (v26.3.4), do not edit directly.
|
||||||
|
// See also: https://pub.dev/packages/pigeon
|
||||||
|
// ignore_for_file: unused_import, unused_shown_name
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data' show Float64List, Int32List, Int64List;
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:meta/meta.dart' show immutable, protected, visibleForTesting;
|
||||||
|
|
||||||
|
Object? _extractReplyValueOrThrow(List<Object?>? replyList, String channelName, {required bool isNullValid}) {
|
||||||
|
if (replyList == null) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'channel-error',
|
||||||
|
message: 'Unable to establish connection on channel: "$channelName".',
|
||||||
|
);
|
||||||
|
} else if (replyList.length > 1) {
|
||||||
|
throw PlatformException(code: replyList[0]! as String, message: replyList[1] as String?, details: replyList[2]);
|
||||||
|
} else if (!isNullValid && (replyList.isNotEmpty && replyList[0] == null)) {
|
||||||
|
throw PlatformException(
|
||||||
|
code: 'null-error',
|
||||||
|
message: 'Host platform returned null value for non-null return value.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return replyList.firstOrNull;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PermissionStatus { granted, denied, permanentlyDenied }
|
||||||
|
|
||||||
|
class _PigeonCodec extends StandardMessageCodec {
|
||||||
|
const _PigeonCodec();
|
||||||
|
@override
|
||||||
|
void writeValue(WriteBuffer buffer, Object? value) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PermissionApi {
|
||||||
|
/// Constructor for [PermissionApi]. The [binaryMessenger] named argument is
|
||||||
|
/// available for dependency injection. If it is left null, the default
|
||||||
|
/// BinaryMessenger will be used which routes to the host platform.
|
||||||
|
PermissionApi({BinaryMessenger? binaryMessenger, String messageChannelSuffix = ''})
|
||||||
|
: pigeonVar_binaryMessenger = binaryMessenger,
|
||||||
|
pigeonVar_messageChannelSuffix = messageChannelSuffix.isNotEmpty ? '.$messageChannelSuffix' : '';
|
||||||
|
final BinaryMessenger? pigeonVar_binaryMessenger;
|
||||||
|
|
||||||
|
static const MessageCodec<Object?> pigeonChannelCodec = _PigeonCodec();
|
||||||
|
|
||||||
|
final String pigeonVar_messageChannelSuffix;
|
||||||
|
|
||||||
|
Future<PermissionStatus> isIgnoringBatteryOptimizations() async {
|
||||||
|
final pigeonVar_channelName =
|
||||||
|
'dev.flutter.pigeon.immich_mobile.PermissionApi.isIgnoringBatteryOptimizations$pigeonVar_messageChannelSuffix';
|
||||||
|
final pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||||
|
pigeonVar_channelName,
|
||||||
|
pigeonChannelCodec,
|
||||||
|
binaryMessenger: pigeonVar_binaryMessenger,
|
||||||
|
);
|
||||||
|
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(null);
|
||||||
|
final pigeonVar_replyList = await pigeonVar_sendFuture as List<Object?>?;
|
||||||
|
|
||||||
|
final Object? pigeonVar_replyValue = _extractReplyValueOrThrow(
|
||||||
|
pigeonVar_replyList,
|
||||||
|
pigeonVar_channelName,
|
||||||
|
isNullValid: false,
|
||||||
|
);
|
||||||
|
return pigeonVar_replyValue! as PermissionStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ import 'package:immich_mobile/providers/background_sync.provider.dart';
|
|||||||
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
import 'package:immich_mobile/providers/backup/drift_backup.provider.dart';
|
||||||
import 'package:immich_mobile/providers/gallery_permission.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/platform.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/server_info.provider.dart';
|
||||||
import 'package:immich_mobile/providers/websocket.provider.dart';
|
import 'package:immich_mobile/providers/websocket.provider.dart';
|
||||||
import 'package:immich_mobile/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ import 'package:immich_mobile/domain/services/background_worker.service.dart';
|
|||||||
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
import 'package:immich_mobile/platform/background_worker_lock_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
import 'package:immich_mobile/platform/connectivity_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
|
||||||
import 'package:immich_mobile/platform/local_image_api.g.dart';
|
import 'package:immich_mobile/platform/local_image_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/native_sync_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/network_api.g.dart';
|
import 'package:immich_mobile/platform/network_api.g.dart';
|
||||||
|
import 'package:immich_mobile/platform/permission_api.g.dart';
|
||||||
import 'package:immich_mobile/platform/remote_image_api.g.dart';
|
import 'package:immich_mobile/platform/remote_image_api.g.dart';
|
||||||
|
|
||||||
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
final backgroundWorkerFgServiceProvider = Provider((_) => BackgroundWorkerFgService(BackgroundWorkerFgHostApi()));
|
||||||
@@ -18,6 +19,8 @@ final nativeSyncApiProvider = Provider<NativeSyncApi>((_) => NativeSyncApi());
|
|||||||
|
|
||||||
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
final connectivityApiProvider = Provider<ConnectivityApi>((_) => ConnectivityApi());
|
||||||
|
|
||||||
|
final permissionApiProvider = Provider<PermissionApi>((_) => PermissionApi());
|
||||||
|
|
||||||
final localImageApi = LocalImageApi();
|
final localImageApi = LocalImageApi();
|
||||||
|
|
||||||
final remoteImageApi = RemoteImageApi();
|
final remoteImageApi = RemoteImageApi();
|
||||||
|
|||||||
+32
@@ -1,6 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
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';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
class NotificationPermissionNotifier extends StateNotifier<PermissionStatus> {
|
class NotificationPermissionNotifier extends StateNotifier<PermissionStatus> {
|
||||||
@@ -39,3 +42,32 @@ class NotificationPermissionNotifier extends StateNotifier<PermissionStatus> {
|
|||||||
final notificationPermissionProvider = StateNotifierProvider<NotificationPermissionNotifier, PermissionStatus>(
|
final notificationPermissionProvider = StateNotifierProvider<NotificationPermissionNotifier, PermissionStatus>(
|
||||||
(ref) => NotificationPermissionNotifier(),
|
(ref) => NotificationPermissionNotifier(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final batteryOptimizationProvider = AsyncNotifierProvider<BatteryOptimizationNotifier, PermissionStatus>(
|
||||||
|
BatteryOptimizationNotifier.new,
|
||||||
|
);
|
||||||
|
|
||||||
|
class BatteryOptimizationNotifier extends AsyncNotifier<PermissionStatus> {
|
||||||
|
Future<PermissionStatus> 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<PermissionStatus> 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/extensions/build_context_extensions.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/services/app_settings.service.dart';
|
import 'package:immich_mobile/services/app_settings.service.dart';
|
||||||
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
import 'package:immich_mobile/utils/hooks/app_settings_update_hook.dart';
|
||||||
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
|
import 'package:immich_mobile/widgets/settings/settings_button_list_tile.dart';
|
||||||
|
|||||||
+8
-14
@@ -30,14 +30,8 @@ run = "dart run build_runner watch --delete-conflicting-outputs"
|
|||||||
alias = "pigeon"
|
alias = "pigeon"
|
||||||
description = "Generate pigeon platform code"
|
description = "Generate pigeon platform code"
|
||||||
run = [
|
run = [
|
||||||
"dart run pigeon --input pigeon/native_sync_api.dart",
|
"ls pigeon/*.dart | xargs -n1 -P4 -I{} dart run pigeon --input {}",
|
||||||
"dart run pigeon --input pigeon/local_image_api.dart",
|
"dart format lib/platform/",
|
||||||
"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",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tasks."codegen:translation"]
|
[tasks."codegen:translation"]
|
||||||
@@ -131,10 +125,10 @@ run = "dcm fix lib"
|
|||||||
|
|
||||||
[tasks.checklist]
|
[tasks.checklist]
|
||||||
run = [
|
run = [
|
||||||
{task = "codegen:pigeon" },
|
{ task = "codegen:pigeon" },
|
||||||
{task = "codegen:dart" },
|
{ task = "codegen:dart" },
|
||||||
{task = "codegen:translation" },
|
{ task = "codegen:translation" },
|
||||||
{task = "analyze" },
|
{ task = "analyze" },
|
||||||
{task = "format" },
|
{ task = "format" },
|
||||||
{task = "test" },
|
{ task = "test" },
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:pigeon/pigeon.dart';
|
||||||
|
|
||||||
|
enum PermissionStatus { granted, denied, permanentlyDenied }
|
||||||
|
|
||||||
|
@ConfigurePigeon(
|
||||||
|
PigeonOptions(
|
||||||
|
dartOut: 'lib/platform/permission_api.g.dart',
|
||||||
|
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/permission/PermissionApi.g.kt',
|
||||||
|
kotlinOptions: KotlinOptions(package: 'app.alextran.immich.permission'),
|
||||||
|
dartOptions: DartOptions(),
|
||||||
|
dartPackageName: 'immich_mobile',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@HostApi()
|
||||||
|
abstract class PermissionApi {
|
||||||
|
PermissionStatus isIgnoringBatteryOptimizations();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user