chore: revert #16732 (#17819)

* chore: revert #16732

* lint
This commit is contained in:
Alex 2025-04-23 16:40:59 -05:00 committed by GitHub
parent 6ce8a1deeb
commit c167e46ec7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 16 additions and 396 deletions

View File

@ -6,7 +6,6 @@
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION" />
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />

View File

@ -1,40 +1,25 @@
package app.alextran.immich
import android.content.ContentResolver
import android.content.ContentUris
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import android.provider.Settings
import android.util.Log
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.PluginRegistry
import java.security.MessageDigest
import java.io.FileInputStream
import kotlinx.coroutines.*
/**
* Android plugin for Dart `BackgroundService` and file trash operations
* Android plugin for Dart `BackgroundService`
*
* Receives messages/method calls from the foreground Dart side to manage
* the background service, e.g. start (enqueue), stop (cancel)
*/
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler, ActivityAware, PluginRegistry.ActivityResultListener {
class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler {
private var methodChannel: MethodChannel? = null
private var fileTrashChannel: MethodChannel? = null
private var context: Context? = null
private var pendingResult: Result? = null
private val PERMISSION_REQUEST_CODE = 1001
private var activityBinding: ActivityPluginBinding? = null
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
@ -44,10 +29,6 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
context = ctx
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
methodChannel?.setMethodCallHandler(this)
// Add file trash channel
fileTrashChannel = MethodChannel(messenger, "file_trash")
fileTrashChannel?.setMethodCallHandler(this)
}
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
@ -57,14 +38,11 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
private fun onDetachedFromEngine() {
methodChannel?.setMethodCallHandler(null)
methodChannel = null
fileTrashChannel?.setMethodCallHandler(null)
fileTrashChannel = null
}
override fun onMethodCall(call: MethodCall, result: Result) {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
val ctx = context!!
when (call.method) {
// Existing BackgroundService methods
"enable" -> {
val args = call.arguments<ArrayList<*>>()!!
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
@ -136,180 +114,10 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
}
}
// File Trash methods moved from MainActivity
"moveToTrash" -> {
val fileName = call.argument<String>("fileName")
if (fileName != null) {
if (hasManageStoragePermission()) {
val success = moveToTrash(fileName)
result.success(success)
} else {
result.error("PERMISSION_DENIED", "Storage permission required", null)
}
} else {
result.error("INVALID_NAME", "The file name is not specified.", null)
}
}
"restoreFromTrash" -> {
val fileName = call.argument<String>("fileName")
if (fileName != null) {
if (hasManageStoragePermission()) {
val success = untrashImage(fileName)
result.success(success)
} else {
result.error("PERMISSION_DENIED", "Storage permission required", null)
}
} else {
result.error("INVALID_NAME", "The file name is not specified.", null)
}
}
"requestManageStoragePermission" -> {
if (!hasManageStoragePermission()) {
requestManageStoragePermission(result)
} else {
Log.e("Manage storage permission", "Permission already granted")
result.success(true)
}
}
else -> result.notImplemented()
}
}
// File Trash methods moved from MainActivity
private fun hasManageStoragePermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
Environment.isExternalStorageManager()
} else {
true
}
}
private fun requestManageStoragePermission(result: Result) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
pendingResult = result // Store the result callback
val activity = activityBinding?.activity ?: return
val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
intent.data = Uri.parse("package:${activity.packageName}")
activity.startActivityForResult(intent, PERMISSION_REQUEST_CODE)
} else {
result.success(true)
}
}
private fun moveToTrash(fileName: String): Boolean {
val contentResolver = context?.contentResolver ?: return false
val uri = getFileUri(fileName)
Log.e("FILE_URI", uri.toString())
return uri?.let { moveToTrash(it) } ?: false
}
private fun moveToTrash(contentUri: Uri): Boolean {
val contentResolver = context?.contentResolver ?: return false
return try {
val values = ContentValues().apply {
put(MediaStore.MediaColumns.IS_TRASHED, 1) // Move to trash
}
val updated = contentResolver.update(contentUri, values, null, null)
updated > 0
} catch (e: Exception) {
Log.e("TrashError", "Error moving to trash", e)
false
}
}
private fun getFileUri(fileName: String): Uri? {
val contentResolver = context?.contentResolver ?: return null
val contentUri = MediaStore.Files.getContentUri("external")
val projection = arrayOf(MediaStore.Images.Media._ID)
val selection = "${MediaStore.Images.Media.DISPLAY_NAME} = ?"
val selectionArgs = arrayOf(fileName)
var fileUri: Uri? = null
contentResolver.query(contentUri, projection, selection, selectionArgs, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID))
fileUri = ContentUris.withAppendedId(contentUri, id)
}
}
return fileUri
}
private fun untrashImage(name: String): Boolean {
val contentResolver = context?.contentResolver ?: return false
val uri = getTrashedFileUri(contentResolver, name)
Log.e("FILE_URI", uri.toString())
return uri?.let { untrashImage(it) } ?: false
}
private fun untrashImage(contentUri: Uri): Boolean {
val contentResolver = context?.contentResolver ?: return false
return try {
val values = ContentValues().apply {
put(MediaStore.MediaColumns.IS_TRASHED, 0) // Restore file
}
val updated = contentResolver.update(contentUri, values, null, null)
updated > 0
} catch (e: Exception) {
Log.e("TrashError", "Error restoring file", e)
false
}
}
private fun getTrashedFileUri(contentResolver: ContentResolver, fileName: String): Uri? {
val contentUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL)
val projection = arrayOf(MediaStore.Files.FileColumns._ID)
val queryArgs = Bundle().apply {
putString(ContentResolver.QUERY_ARG_SQL_SELECTION, "${MediaStore.Files.FileColumns.DISPLAY_NAME} = ?")
putStringArray(ContentResolver.QUERY_ARG_SQL_SELECTION_ARGS, arrayOf(fileName))
putInt(MediaStore.QUERY_ARG_MATCH_TRASHED, MediaStore.MATCH_ONLY)
}
contentResolver.query(contentUri, projection, queryArgs, null)?.use { cursor ->
if (cursor.moveToFirst()) {
val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID))
return ContentUris.withAppendedId(contentUri, id)
}
}
return null
}
// ActivityAware implementation
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activityBinding = binding
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivityForConfigChanges() {
activityBinding?.removeActivityResultListener(this)
activityBinding = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activityBinding = binding
binding.addActivityResultListener(this)
}
override fun onDetachedFromActivity() {
activityBinding?.removeActivityResultListener(this)
activityBinding = null
}
// ActivityResultListener implementation
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean {
if (requestCode == PERMISSION_REQUEST_CODE) {
val granted = hasManageStoragePermission()
pendingResult?.success(granted)
pendingResult = null
return true
}
return false
}
}
private const val TAG = "BackgroundServicePlugin"
private const val BUFFER_SIZE = 2 * 1024 * 1024
private const val BUFFER_SIZE = 2 * 1024 * 1024;

View File

@ -2,12 +2,14 @@ package app.alextran.immich
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import androidx.annotation.NonNull
import android.os.Bundle
import android.content.Intent
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
flutterEngine.plugins.add(BackgroundServicePlugin())
// No need to set up method channel here as it's now handled in the plugin
}
}

View File

@ -65,7 +65,6 @@ enum StoreKey<T> {
// Video settings
loadOriginalVideo<bool>._(136),
manageLocalMediaAndroid<bool>._(137),
// Experimental stuff
photoManagerCustomFilter<bool>._(1000);

View File

@ -1,5 +0,0 @@
abstract interface class ILocalFilesManager {
Future<bool> moveToTrash(String fileName);
Future<bool> restoreFromTrash(String fileName);
Future<bool> requestManageStoragePermission();
}

View File

@ -23,7 +23,6 @@ enum PendingAction {
assetDelete,
assetUploaded,
assetHidden,
assetTrash,
}
class PendingChange {
@ -161,7 +160,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('on_upload_success', _handleOnUploadSuccess);
socket.on('on_config_update', _handleOnConfigUpdate);
socket.on('on_asset_delete', _handleOnAssetDelete);
socket.on('on_asset_trash', _handleOnAssetTrash);
socket.on('on_asset_trash', _handleServerUpdates);
socket.on('on_asset_restore', _handleServerUpdates);
socket.on('on_asset_update', _handleServerUpdates);
socket.on('on_asset_stack_update', _handleServerUpdates);
@ -208,26 +207,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
_debounce.run(handlePendingChanges);
}
Future<void> _handlePendingTrashes() async {
final trashChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetTrash)
.toList();
if (trashChanges.isNotEmpty) {
List<String> remoteIds = trashChanges
.expand((a) => (a.value as List).map((e) => e.toString()))
.toList();
await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
await _ref.read(assetProvider.notifier).getAllAsset();
state = state.copyWith(
pendingChanges: state.pendingChanges
.whereNot((c) => trashChanges.contains(c))
.toList(),
);
}
}
Future<void> _handlePendingDeletes() async {
final deleteChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetDelete)
@ -288,7 +267,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
await _handlePendingUploaded();
await _handlePendingDeletes();
await _handlingPendingHidden();
await _handlePendingTrashes();
}
void _handleOnConfigUpdate(dynamic _) {
@ -307,10 +285,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
void _handleOnAssetDelete(dynamic data) =>
addPendingChange(PendingAction.assetDelete, data);
void _handleOnAssetTrash(dynamic data) {
addPendingChange(PendingAction.assetTrash, data);
}
void _handleOnAssetHidden(dynamic data) =>
addPendingChange(PendingAction.assetHidden, data);

View File

@ -1,23 +0,0 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/utils/local_files_manager.dart';
final localFilesManagerRepositoryProvider =
Provider((ref) => LocalFilesManagerRepository());
class LocalFilesManagerRepository implements ILocalFilesManager {
@override
Future<bool> moveToTrash(String fileName) async {
return await LocalFilesManager.moveToTrash(fileName);
}
@override
Future<bool> restoreFromTrash(String fileName) async {
return await LocalFilesManager.restoreFromTrash(fileName);
}
@override
Future<bool> requestManageStoragePermission() async {
return await LocalFilesManager.requestManageStoragePermission();
}
}

View File

@ -61,7 +61,6 @@ enum AppSettingsEnum<T> {
0,
),
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),

View File

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
@ -17,10 +16,8 @@ import 'package:immich_mobile/interfaces/album_api.interface.dart';
import 'package:immich_mobile/interfaces/album_media.interface.dart';
import 'package:immich_mobile/interfaces/asset.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:immich_mobile/providers/app_settings.provider.dart';
import 'package:immich_mobile/providers/infrastructure/exif.provider.dart';
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
import 'package:immich_mobile/repositories/album.repository.dart';
@ -28,10 +25,8 @@ import 'package:immich_mobile/repositories/album_api.repository.dart';
import 'package:immich_mobile/repositories/album_media.repository.dart';
import 'package:immich_mobile/repositories/asset.repository.dart';
import 'package:immich_mobile/repositories/etag.repository.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.dart';
import 'package:immich_mobile/repositories/partner.repository.dart';
import 'package:immich_mobile/repositories/partner_api.repository.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/entity.service.dart';
import 'package:immich_mobile/services/hash.service.dart';
import 'package:immich_mobile/utils/async_mutex.dart';
@ -53,8 +48,6 @@ final syncServiceProvider = Provider(
ref.watch(userRepositoryProvider),
ref.watch(userServiceProvider),
ref.watch(etagRepositoryProvider),
ref.watch(appSettingsServiceProvider),
ref.watch(localFilesManagerRepositoryProvider),
ref.watch(partnerApiRepositoryProvider),
ref.watch(userApiRepositoryProvider),
),
@ -76,8 +69,6 @@ class SyncService {
final IUserApiRepository _userApiRepository;
final AsyncMutex _lock = AsyncMutex();
final Logger _log = Logger('SyncService');
final AppSettingsService _appSettingsService;
final ILocalFilesManager _localFilesManager;
SyncService(
this._hashService,
@ -91,8 +82,6 @@ class SyncService {
this._userRepository,
this._userService,
this._eTagRepository,
this._appSettingsService,
this._localFilesManager,
this._partnerApiRepository,
this._userApiRepository,
);
@ -249,19 +238,8 @@ class SyncService {
return null;
}
Future<void> _moveToTrashMatchedAssets(Iterable<String> idsToDelete) async {
final List<Asset> localAssets = await _assetRepository.getAllLocal();
final List<Asset> matchedAssets = localAssets
.where((asset) => idsToDelete.contains(asset.remoteId))
.toList();
for (var asset in matchedAssets) {
_localFilesManager.moveToTrash(asset.fileName);
}
}
/// Deletes remote-only assets, updates merged assets to be local-only
Future<void> handleRemoteAssetRemoval(List<String> idsToDelete) async {
Future<void> handleRemoteAssetRemoval(List<String> idsToDelete) {
return _assetRepository.transaction(() async {
await _assetRepository.deleteAllByRemoteId(
idsToDelete,
@ -271,12 +249,6 @@ class SyncService {
idsToDelete,
state: AssetState.merged,
);
if (Platform.isAndroid &&
_appSettingsService.getSetting<bool>(
AppSettingsEnum.manageLocalMediaAndroid,
)) {
await _moveToTrashMatchedAssets(idsToDelete);
}
if (merged.isEmpty) return;
for (final Asset asset in merged) {
asset.remoteId = null;
@ -818,27 +790,10 @@ class SyncService {
return (existing, toUpsert);
}
Future<void> _toggleTrashStatusForAssets(List<Asset> assetsList) async {
for (var asset in assetsList) {
if (asset.isTrashed) {
_localFilesManager.moveToTrash(asset.fileName);
} else {
_localFilesManager.restoreFromTrash(asset.fileName);
}
}
}
/// Inserts or updates the assets in the database with their ExifInfo (if any)
Future<void> upsertAssetsWithExif(List<Asset> assets) async {
if (assets.isEmpty) return;
if (Platform.isAndroid &&
_appSettingsService.getSetting<bool>(
AppSettingsEnum.manageLocalMediaAndroid,
)) {
_toggleTrashStatusForAssets(assets);
}
try {
await _assetRepository.transaction(() async {
await _assetRepository.updateAll(assets);

View File

@ -1,39 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class LocalFilesManager {
static const MethodChannel _channel = MethodChannel('file_trash');
static Future<bool> moveToTrash(String fileName) async {
try {
final bool success =
await _channel.invokeMethod('moveToTrash', {'fileName': fileName});
return success;
} on PlatformException catch (e) {
debugPrint('Error moving to trash: ${e.message}');
return false;
}
}
static Future<bool> restoreFromTrash(String fileName) async {
try {
final bool success = await _channel
.invokeMethod('restoreFromTrash', {'fileName': fileName});
return success;
} on PlatformException catch (e) {
debugPrint('Error restoring file: ${e.message}');
return false;
}
}
static Future<bool> requestManageStoragePermission() async {
try {
final bool success =
await _channel.invokeMethod('requestManageStoragePermission');
return success;
} on PlatformException catch (e) {
debugPrint('Error requesting permission: ${e.message}');
return false;
}
}
}

View File

@ -1,13 +1,11 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/domain/services/log.service.dart';
import 'package:immich_mobile/providers/user.provider.dart';
import 'package:immich_mobile/repositories/local_files_manager.repository.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/http_ssl_cert_override.dart';
@ -27,8 +25,6 @@ class AdvancedSettings extends HookConsumerWidget {
final advancedTroubleshooting =
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
final manageLocalMediaAndroid =
useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
final allowSelfSignedSSLCert =
@ -44,16 +40,6 @@ class AdvancedSettings extends HookConsumerWidget {
LogService.I.setlogLevel(Level.LEVELS[levelId.value].toLogLevel()),
);
Future<bool> checkAndroidVersion() async {
if (Platform.isAndroid) {
DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
int sdkVersion = androidInfo.version.sdkInt;
return sdkVersion >= 30;
}
return false;
}
final advancedSettings = [
SettingsSwitchListTile(
enabled: true,
@ -61,29 +47,6 @@ class AdvancedSettings extends HookConsumerWidget {
title: "advanced_settings_troubleshooting_title".tr(),
subtitle: "advanced_settings_troubleshooting_subtitle".tr(),
),
FutureBuilder<bool>(
future: checkAndroidVersion(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) {
return SettingsSwitchListTile(
enabled: true,
valueNotifier: manageLocalMediaAndroid,
title: "advanced_settings_sync_remote_deletions_title".tr(),
subtitle: "advanced_settings_sync_remote_deletions_subtitle".tr(),
onChanged: (value) async {
if (value) {
final result = await ref
.read(localFilesManagerRepositoryProvider)
.requestManageStoragePermission();
manageLocalMediaAndroid.value = result;
}
},
);
} else {
return const SizedBox.shrink();
}
},
),
SettingsSliderListTile(
text: "advanced_settings_log_level_title".tr(args: [logLevel]),
valueNotifier: levelId,

View File

@ -60,9 +60,6 @@ void main() {
final MockAlbumMediaRepository albumMediaRepository =
MockAlbumMediaRepository();
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
final MockAppSettingService appSettingService = MockAppSettingService();
final MockLocalFilesManagerRepository localFilesManagerRepository =
MockLocalFilesManagerRepository();
final MockPartnerApiRepository partnerApiRepository =
MockPartnerApiRepository();
final MockUserApiRepository userApiRepository = MockUserApiRepository();
@ -109,8 +106,6 @@ void main() {
userRepository,
userService,
eTagRepository,
appSettingService,
localFilesManagerRepository,
partnerApiRepository,
userApiRepository,
);

View File

@ -10,7 +10,6 @@ import 'package:immich_mobile/interfaces/auth_api.interface.dart';
import 'package:immich_mobile/interfaces/backup_album.interface.dart';
import 'package:immich_mobile/interfaces/etag.interface.dart';
import 'package:immich_mobile/interfaces/file_media.interface.dart';
import 'package:immich_mobile/interfaces/local_files_manager.interface.dart';
import 'package:immich_mobile/interfaces/partner.interface.dart';
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
import 'package:mocktail/mocktail.dart';
@ -42,9 +41,6 @@ class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
class MockAuthRepository extends Mock implements IAuthRepository {}
class MockPartnerRepository extends Mock implements IPartnerRepository {}
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
class MockLocalFilesManagerRepository extends Mock
implements ILocalFilesManager {}
class MockPartnerRepository extends Mock implements IPartnerRepository {}

View File

@ -1,6 +1,5 @@
import 'package:immich_mobile/services/album.service.dart';
import 'package:immich_mobile/services/api.service.dart';
import 'package:immich_mobile/services/app_settings.service.dart';
import 'package:immich_mobile/services/background.service.dart';
import 'package:immich_mobile/services/backup.service.dart';
import 'package:immich_mobile/services/entity.service.dart';
@ -26,6 +25,4 @@ class MockNetworkService extends Mock implements NetworkService {}
class MockSearchApi extends Mock implements SearchApi {}
class MockAppSettingService extends Mock implements AppSettingsService {}
class MockBackgroundService extends Mock implements BackgroundService {}