Merge bf4b3ab588 into 77926383db
commit
6c694f16f5
|
|
@ -2163,6 +2163,10 @@
|
|||
"uploading_media": "Uploading media",
|
||||
"url": "URL",
|
||||
"usage": "Usage",
|
||||
"use_as_wallpaper": "Set as wallpaper",
|
||||
"use_as_wallpaper_failed": "Failed to set wallpaper",
|
||||
"use_as_wallpaper_image_only": "Only images can be set as wallpaper",
|
||||
"use_as_wallpaper_not_supported": "Set as wallpaper is currently only supported on Android",
|
||||
"use_biometric": "Use biometric",
|
||||
"use_current_connection": "use current connection",
|
||||
"use_custom_date_range": "Use custom date range instead",
|
||||
|
|
|
|||
|
|
@ -2138,6 +2138,10 @@
|
|||
"uploading_media": "Przesyłanie multimediów",
|
||||
"url": "URL",
|
||||
"usage": "Użycie",
|
||||
"use_as_wallpaper": "Ustaw jako tło ekranu",
|
||||
"use_as_wallpaper_failed": "Nie udało się ustawić jako tła ekranu",
|
||||
"use_as_wallpaper_image_only": "Tylko obrazy mogą być ustawione jako tło ekranu",
|
||||
"use_as_wallpaper_not_supported": "Ustawienie jako tło ekranu jest aktualnie obsługiwane tylko na Androidzie",
|
||||
"use_biometric": "Użyj biometrii",
|
||||
"use_current_connection": "użyj bieżącego połączenia",
|
||||
"use_custom_date_range": "Zamiast tego użyj niestandardowego zakresu dat",
|
||||
|
|
|
|||
|
|
@ -144,6 +144,17 @@
|
|||
android:authorities="${applicationId}.androidx-startup"
|
||||
tools:node="remove" />
|
||||
|
||||
<!-- FileProvider for sharing files with other apps -->
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
|
||||
|
||||
<!-- Widgets -->
|
||||
<receiver
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ import app.alextran.immich.images.ThumbnailsImpl
|
|||
import app.alextran.immich.sync.NativeSyncApi
|
||||
import app.alextran.immich.sync.NativeSyncApiImpl26
|
||||
import app.alextran.immich.sync.NativeSyncApiImpl30
|
||||
import app.alextran.immich.use_as_wallpaper.UseAsWallpaperApi
|
||||
import app.alextran.immich.use_as_wallpaper.UseAsWallpaperApiImpl
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
|
||||
|
|
@ -39,6 +41,7 @@ class MainActivity : FlutterFragmentActivity() {
|
|||
ThumbnailApi.setUp(messenger, ThumbnailsImpl(ctx))
|
||||
BackgroundWorkerFgHostApi.setUp(messenger, BackgroundWorkerApiImpl(ctx))
|
||||
ConnectivityApi.setUp(messenger, ConnectivityApiImpl(ctx))
|
||||
UseAsWallpaperApi.setUp(messenger, UseAsWallpaperApiImpl(ctx))
|
||||
|
||||
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||
flutterEngine.plugins.add(HttpSSLOptionsPlugin())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
@file:Suppress("UNCHECKED_CAST", "ArrayInDataClass")
|
||||
|
||||
package app.alextran.immich.use_as_wallpaper
|
||||
|
||||
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 MessagesPigeonUtils {
|
||||
|
||||
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
|
||||
) : Throwable()
|
||||
private open class MessagesPigeonCodec : StandardMessageCodec() {
|
||||
override fun readValueOfType(type: Byte, buffer: ByteBuffer): Any? {
|
||||
return super.readValueOfType(type, buffer)
|
||||
}
|
||||
override fun writeValue(stream: ByteArrayOutputStream, value: Any?) {
|
||||
super.writeValue(stream, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
|
||||
interface UseAsWallpaperApi {
|
||||
fun useAsWallpaper(filePath: String, callback: (Result<Boolean>) -> Unit)
|
||||
|
||||
companion object {
|
||||
/** The codec used by UseAsWallpaperApi. */
|
||||
val codec: MessageCodec<Any?> by lazy {
|
||||
MessagesPigeonCodec()
|
||||
}
|
||||
/** Sets up an instance of `UseAsWallpaperApi` to handle messages through the `binaryMessenger`. */
|
||||
@JvmOverloads
|
||||
fun setUp(binaryMessenger: BinaryMessenger, api: UseAsWallpaperApi?, messageChannelSuffix: String = "") {
|
||||
val separatedMessageChannelSuffix = if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
|
||||
run {
|
||||
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.immich_mobile.UseAsWallpaperApi.useAsWallpaper$separatedMessageChannelSuffix", codec)
|
||||
if (api != null) {
|
||||
channel.setMessageHandler { message, reply ->
|
||||
val args = message as List<Any?>
|
||||
val filePathArg = args[0] as String
|
||||
api.useAsWallpaper(filePathArg) { result: Result<Boolean> ->
|
||||
val error = result.exceptionOrNull()
|
||||
if (error != null) {
|
||||
reply.reply(MessagesPigeonUtils.wrapError(error))
|
||||
} else {
|
||||
val data = result.getOrNull()
|
||||
reply.reply(MessagesPigeonUtils.wrapResult(data))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel.setMessageHandler(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package app.alextran.immich.use_as_wallpaper
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
|
||||
class UseAsWallpaperApiImpl(private val context: Context) : UseAsWallpaperApi {
|
||||
override fun useAsWallpaper(filePath: String, callback: (Result<Boolean>) -> Unit) {
|
||||
try {
|
||||
val file = File(filePath)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
context.packageName + ".provider",
|
||||
file
|
||||
)
|
||||
|
||||
val intent = Intent(Intent.ACTION_ATTACH_DATA).apply {
|
||||
setDataAndType(uri, "image/*")
|
||||
putExtra("mimeType", "image/*")
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
context.startActivity(Intent.createChooser(intent, "Set as Wallpaper"))
|
||||
|
||||
callback(Result.success(true))
|
||||
} catch (e: Exception) {
|
||||
Log.e("UseAsWallpaperApiImpl", "Failed to launch wallpaper intent", e)
|
||||
callback(Result.success(false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths>
|
||||
<!-- Cache directory for temporary files -->
|
||||
<cache-path name="cache" path="." />
|
||||
|
||||
<!-- External cache directory -->
|
||||
<external-cache-path name="external_cache" path="." />
|
||||
|
||||
<!-- Files directory -->
|
||||
<files-path name="files" path="." />
|
||||
|
||||
<!-- External files directory -->
|
||||
<external-files-path name="external_files" path="." />
|
||||
|
||||
<!-- External storage (for downloaded images) -->
|
||||
<external-path name="external" path="." />
|
||||
</paths>
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
// Autogenerated from Pigeon (v26.0.2), do not edit directly.
|
||||
// See also: https://pub.dev/packages/pigeon
|
||||
// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import, no_leading_underscores_for_local_identifiers
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List;
|
||||
|
||||
import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer;
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
PlatformException _createConnectionError(String channelName) {
|
||||
return PlatformException(
|
||||
code: 'channel-error',
|
||||
message: 'Unable to establish connection on channel: "$channelName".',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
class _PigeonCodec extends StandardMessageCodec {
|
||||
const _PigeonCodec();
|
||||
@override
|
||||
void writeValue(WriteBuffer buffer, Object? value) {
|
||||
if (value is int) {
|
||||
buffer.putUint8(4);
|
||||
buffer.putInt64(value);
|
||||
} else {
|
||||
super.writeValue(buffer, value);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Object? readValueOfType(int type, ReadBuffer buffer) {
|
||||
switch (type) {
|
||||
default:
|
||||
return super.readValueOfType(type, buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class UseAsWallpaperApi {
|
||||
/// Constructor for [UseAsWallpaperApi]. 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.
|
||||
UseAsWallpaperApi({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<bool> useAsWallpaper({required String filePath}) async {
|
||||
final String pigeonVar_channelName = 'dev.flutter.pigeon.immich_mobile.UseAsWallpaperApi.useAsWallpaper$pigeonVar_messageChannelSuffix';
|
||||
final BasicMessageChannel<Object?> pigeonVar_channel = BasicMessageChannel<Object?>(
|
||||
pigeonVar_channelName,
|
||||
pigeonChannelCodec,
|
||||
binaryMessenger: pigeonVar_binaryMessenger,
|
||||
);
|
||||
final Future<Object?> pigeonVar_sendFuture = pigeonVar_channel.send(<Object?>[filePath]);
|
||||
final List<Object?>? pigeonVar_replyList =
|
||||
await pigeonVar_sendFuture as List<Object?>?;
|
||||
if (pigeonVar_replyList == null) {
|
||||
throw _createConnectionError(pigeonVar_channelName);
|
||||
} else if (pigeonVar_replyList.length > 1) {
|
||||
throw PlatformException(
|
||||
code: pigeonVar_replyList[0]! as String,
|
||||
message: pigeonVar_replyList[1] as String?,
|
||||
details: pigeonVar_replyList[2],
|
||||
);
|
||||
} else if (pigeonVar_replyList[0] == null) {
|
||||
throw PlatformException(
|
||||
code: 'null-error',
|
||||
message: 'Host platform returned null value for non-null return value.',
|
||||
);
|
||||
} else {
|
||||
return (pigeonVar_replyList[0] as bool?)!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
import 'package:immich_mobile/services/wallpaper.service.dart';
|
||||
import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class UseAsWallpaperActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
const UseAsWallpaperActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement iOS support
|
||||
if (!Platform.isAndroid) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'use_as_wallpaper_not_supported'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final asset = _getAsset(ref);
|
||||
if (asset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!asset.isImage) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'use_as_wallpaper_image_only'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final wallpaperService = ref.read(wallpaperServiceProvider);
|
||||
final result = await wallpaperService.setAsWallpaper(asset);
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'use_as_wallpaper_failed'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImmichToast.show(
|
||||
context: context,
|
||||
msg: 'use_as_wallpaper_failed'.t(context: context),
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
toastType: ToastType.error,
|
||||
);
|
||||
} finally {
|
||||
if (source == ActionSource.timeline) {
|
||||
ref.read(multiSelectProvider.notifier).reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BaseAsset? _getAsset(WidgetRef ref) {
|
||||
return switch (source) {
|
||||
ActionSource.timeline => () {
|
||||
final selectedAssets = ref.read(multiSelectProvider).selectedAssets;
|
||||
if (selectedAssets.length == 1) {
|
||||
return selectedAssets.first;
|
||||
}
|
||||
return null;
|
||||
}(),
|
||||
ActionSource.viewer => ref.read(currentAssetNotifier),
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
// TODO: Implement iOS support - hide button on iOS for now
|
||||
if (!Platform.isAndroid) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return BaseActionButton(
|
||||
iconData: Icons.wallpaper,
|
||||
maxWidth: 95,
|
||||
label: 'use_as_wallpaper'.t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/stack_action_b
|
|||
import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/use_as_wallpaper_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/album/album_selector.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_sheet.widget.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/album.provider.dart';
|
||||
|
|
@ -121,6 +122,8 @@ class _GeneralBottomSheetState extends ConsumerState<GeneralBottomSheet> {
|
|||
if (multiselect.hasStacked) const UnStackActionButton(source: ActionSource.timeline),
|
||||
const DeleteActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
if (multiselect.selectedAssets.length == 1 && multiselect.selectedAssets.first.isImage)
|
||||
const UseAsWallpaperActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasLocal || multiselect.hasMerged) const DeleteLocalActionButton(source: ActionSource.timeline),
|
||||
if (multiselect.hasLocal) const UploadActionButton(source: ActionSource.timeline),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/platform/use_as_wallpaper_api.g.dart';
|
||||
import 'package:immich_mobile/repositories/asset_api.repository.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
|
||||
final wallpaperServiceProvider = Provider<WallpaperService>(
|
||||
(ref) => WallpaperService(ref.watch(assetApiRepositoryProvider)),
|
||||
);
|
||||
|
||||
class WallpaperService {
|
||||
final AssetApiRepository _assetApiRepository;
|
||||
final Logger _log = Logger("WallpaperService");
|
||||
final UseAsWallpaperApi _wallpaperApi = UseAsWallpaperApi();
|
||||
|
||||
WallpaperService(this._assetApiRepository);
|
||||
|
||||
/// Sets the given asset as wallpaper.
|
||||
/// Returns true if the wallpaper intent was launched successfully.
|
||||
/// Note: This only works on Android.
|
||||
Future<bool> setAsWallpaper(BaseAsset asset) async {
|
||||
// TODO: Implement iOS support
|
||||
if (!Platform.isAndroid) {
|
||||
_log.warning('Set as wallpaper is currently only supported on Android');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!asset.isImage) {
|
||||
_log.warning('Cannot set non-image asset as wallpaper');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
final filePath = await _getAssetFilePath(asset);
|
||||
if (filePath == null) {
|
||||
_log.severe('Failed to get file path for asset');
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await _wallpaperApi.useAsWallpaper(filePath: filePath);
|
||||
|
||||
// Clean up temp file if it was a remote asset
|
||||
if (asset is RemoteAsset && asset.localId == null) {
|
||||
await _cleanupTempFile(filePath);
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (e, stack) {
|
||||
_log.severe('Failed to set wallpaper', e, stack);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the file path for an asset.
|
||||
/// For local assets, retrieves the original file path.
|
||||
/// For remote assets, downloads to a temp file and returns the path.
|
||||
Future<String?> _getAssetFilePath(BaseAsset asset) async {
|
||||
// Try to get local file first
|
||||
final localId = asset is LocalAsset
|
||||
? asset.id
|
||||
: asset is RemoteAsset
|
||||
? asset.localId
|
||||
: null;
|
||||
|
||||
if (localId != null) {
|
||||
try {
|
||||
final assetEntity = AssetEntity(id: localId, width: 1, height: 1, typeInt: 0);
|
||||
final file = await assetEntity.originFile;
|
||||
if (file != null && await file.exists()) {
|
||||
return file.path;
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning('Failed to get local file for asset: $e');
|
||||
}
|
||||
}
|
||||
|
||||
// Download remote asset to temp file
|
||||
if (asset is RemoteAsset) {
|
||||
return await _downloadToTempFile(asset);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Downloads a remote asset to a temp file and returns the path.
|
||||
Future<String?> _downloadToTempFile(RemoteAsset asset) async {
|
||||
try {
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempFile = File('${tempDir.path}/wallpaper_${asset.id}_${asset.name}');
|
||||
|
||||
final res = await _assetApiRepository.downloadAsset(asset.id);
|
||||
|
||||
if (res.statusCode != 200) {
|
||||
_log.severe('Failed to download asset for wallpaper: ${res.statusCode}');
|
||||
return null;
|
||||
}
|
||||
|
||||
await tempFile.writeAsBytes(res.bodyBytes);
|
||||
return tempFile.path;
|
||||
} catch (e, stack) {
|
||||
_log.severe('Failed to download asset for wallpaper', e, stack);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Cleans up a temp file after it's no longer needed.
|
||||
Future<void> _cleanupTempFile(String filePath) async {
|
||||
try {
|
||||
final file = File(filePath);
|
||||
if (await file.exists()) {
|
||||
// Delay cleanup slightly to allow the system to read the file
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
await file.delete();
|
||||
}
|
||||
} catch (e) {
|
||||
_log.warning('Failed to cleanup temp file: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
|
@ -28,6 +30,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_b
|
|||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/use_as_wallpaper_action_button.widget.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class ActionButtonContext {
|
||||
|
|
@ -62,6 +65,7 @@ enum ActionButtonType {
|
|||
archive,
|
||||
unarchive,
|
||||
download,
|
||||
useAsWallpaper,
|
||||
trash,
|
||||
deletePermanent,
|
||||
delete,
|
||||
|
|
@ -94,6 +98,9 @@ enum ActionButtonType {
|
|||
!context.isInLockedView && //
|
||||
context.asset.hasRemote && //
|
||||
!context.asset.hasLocal,
|
||||
ActionButtonType.useAsWallpaper =>
|
||||
(context.asset.type == AssetType.image) && //
|
||||
Platform.isAndroid, // TODO: remove when iOS is supported
|
||||
ActionButtonType.trash =>
|
||||
context.isOwner && //
|
||||
!context.isInLockedView && //
|
||||
|
|
@ -149,6 +156,7 @@ enum ActionButtonType {
|
|||
ActionButtonType.archive => ArchiveActionButton(source: context.source),
|
||||
ActionButtonType.unarchive => UnArchiveActionButton(source: context.source),
|
||||
ActionButtonType.download => DownloadActionButton(source: context.source),
|
||||
ActionButtonType.useAsWallpaper => UseAsWallpaperActionButton(source: context.source),
|
||||
ActionButtonType.trash => TrashActionButton(source: context.source),
|
||||
ActionButtonType.deletePermanent => DeletePermanentActionButton(source: context.source),
|
||||
ActionButtonType.delete => DeleteActionButton(source: context.source),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
|
||||
import 'package:pigeon/pigeon.dart';
|
||||
|
||||
@ConfigurePigeon(
|
||||
PigeonOptions(
|
||||
dartOut: 'lib/platform/use_as_wallpaper_api.g.dart',
|
||||
kotlinOut: 'android/app/src/main/kotlin/app/alextran/immich/use_as_wallpaper/Messages.g.kt',
|
||||
kotlinOptions: KotlinOptions(
|
||||
package: 'app.alextran.immich.use_as_wallpaper',
|
||||
),
|
||||
dartPackageName: 'immich_mobile',
|
||||
),
|
||||
)
|
||||
|
||||
@HostApi()
|
||||
abstract class UseAsWallpaperApi {
|
||||
@async
|
||||
bool useAsWallpaper({required String filePath});
|
||||
}
|
||||
Loading…
Reference in New Issue