mirror of
https://github.com/immich-app/immich.git
synced 2025-07-09 03:04:16 -04:00
Merge branch 'improve_focus' into keynav_timeline
This commit is contained in:
commit
48f9b5b981
6
cli/package-lock.json
generated
6
cli/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.62",
|
"version": "2.2.63",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.62",
|
"version": "2.2.63",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": "^4.0.3",
|
"chokidar": "^4.0.3",
|
||||||
@ -54,7 +54,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.62",
|
"version": "2.2.63",
|
||||||
"description": "Command Line Interface (CLI) for Immich",
|
"description": "Command Line Interface (CLI) for Immich",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"exports": "./dist/index.js",
|
"exports": "./dist/index.js",
|
||||||
|
4
docs/static/archived-versions.json
vendored
4
docs/static/archived-versions.json
vendored
@ -1,4 +1,8 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"label": "v1.132.1",
|
||||||
|
"url": "https://v1.132.1.archive.immich.app"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "v1.132.0",
|
"label": "v1.132.0",
|
||||||
"url": "https://v1.132.0.archive.immich.app"
|
"url": "https://v1.132.0.archive.immich.app"
|
||||||
|
8
e2e/package-lock.json
generated
8
e2e/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3.1.0",
|
"@eslint/eslintrc": "^3.1.0",
|
||||||
@ -44,7 +44,7 @@
|
|||||||
},
|
},
|
||||||
"../cli": {
|
"../cli": {
|
||||||
"name": "@immich/cli",
|
"name": "@immich/cli",
|
||||||
"version": "2.2.62",
|
"version": "2.2.63",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -93,7 +93,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-e2e",
|
"name": "immich-e2e",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
android:maxSdkVersion="32" />
|
android:maxSdkVersion="32" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="32" />
|
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.ACCESS_MEDIA_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
<uses-permission android:name="android.permission.MANAGE_MEDIA" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
|
@ -1,40 +1,25 @@
|
|||||||
package app.alextran.immich
|
package app.alextran.immich
|
||||||
|
|
||||||
import android.content.ContentResolver
|
|
||||||
import android.content.ContentUris
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
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 android.util.Log
|
||||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
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.BinaryMessenger
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel
|
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.security.MessageDigest
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import kotlinx.coroutines.*
|
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 methodChannel: MethodChannel? = null
|
||||||
private var fileTrashChannel: MethodChannel? = null
|
|
||||||
private var context: Context? = 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) {
|
override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
onAttachedToEngine(binding.applicationContext, binding.binaryMessenger)
|
||||||
@ -44,10 +29,6 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
context = ctx
|
context = ctx
|
||||||
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
|
methodChannel = MethodChannel(messenger, "immich/foregroundChannel")
|
||||||
methodChannel?.setMethodCallHandler(this)
|
methodChannel?.setMethodCallHandler(this)
|
||||||
|
|
||||||
// Add file trash channel
|
|
||||||
fileTrashChannel = MethodChannel(messenger, "file_trash")
|
|
||||||
fileTrashChannel?.setMethodCallHandler(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
|
||||||
@ -57,14 +38,11 @@ class BackgroundServicePlugin : FlutterPlugin, MethodChannel.MethodCallHandler,
|
|||||||
private fun onDetachedFromEngine() {
|
private fun onDetachedFromEngine() {
|
||||||
methodChannel?.setMethodCallHandler(null)
|
methodChannel?.setMethodCallHandler(null)
|
||||||
methodChannel = 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!!
|
val ctx = context!!
|
||||||
when (call.method) {
|
when (call.method) {
|
||||||
// Existing BackgroundService methods
|
|
||||||
"enable" -> {
|
"enable" -> {
|
||||||
val args = call.arguments<ArrayList<*>>()!!
|
val args = call.arguments<ArrayList<*>>()!!
|
||||||
ctx.getSharedPreferences(BackupWorker.SHARED_PREF_NAME, Context.MODE_PRIVATE)
|
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()
|
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 TAG = "BackgroundServicePlugin"
|
||||||
private const val BUFFER_SIZE = 2 * 1024 * 1024
|
private const val BUFFER_SIZE = 2 * 1024 * 1024;
|
||||||
|
@ -2,12 +2,14 @@ package app.alextran.immich
|
|||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import androidx.annotation.NonNull
|
import android.os.Bundle
|
||||||
|
import android.content.Intent
|
||||||
|
|
||||||
class MainActivity : FlutterActivity() {
|
class MainActivity : FlutterActivity() {
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
|
||||||
|
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
flutterEngine.plugins.add(BackgroundServicePlugin())
|
flutterEngine.plugins.add(BackgroundServicePlugin())
|
||||||
// No need to set up method channel here as it's now handled in the plugin
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,8 +35,8 @@ platform :android do
|
|||||||
task: 'bundle',
|
task: 'bundle',
|
||||||
build_type: 'Release',
|
build_type: 'Release',
|
||||||
properties: {
|
properties: {
|
||||||
"android.injected.version.code" => 194,
|
"android.injected.version.code" => 195,
|
||||||
"android.injected.version.name" => "1.132.0",
|
"android.injected.version.name" => "1.132.1",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
upload_to_play_store(skip_upload_apk: true, skip_upload_images: true, skip_upload_screenshots: true, aab: '../build/app/outputs/bundle/release/app-release.aab')
|
||||||
|
@ -541,7 +541,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@ -685,7 +685,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@ -715,7 +715,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
@ -748,7 +748,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@ -791,7 +791,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
@ -831,7 +831,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
CODE_SIGN_ENTITLEMENTS = ShareExtension/ShareExtension.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 201;
|
CURRENT_PROJECT_VERSION = 202;
|
||||||
CUSTOM_GROUP_ID = group.app.immich.share;
|
CUSTOM_GROUP_ID = group.app.immich.share;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||||
|
@ -78,7 +78,7 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.131.3</string>
|
<string>1.132.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
@ -93,7 +93,7 @@
|
|||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>201</string>
|
<string>202</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
@ -19,7 +19,7 @@ platform :ios do
|
|||||||
desc "iOS Release"
|
desc "iOS Release"
|
||||||
lane :release do
|
lane :release do
|
||||||
increment_version_number(
|
increment_version_number(
|
||||||
version_number: "1.132.0"
|
version_number: "1.132.1"
|
||||||
)
|
)
|
||||||
increment_build_number(
|
increment_build_number(
|
||||||
build_number: latest_testflight_build_number + 1,
|
build_number: latest_testflight_build_number + 1,
|
||||||
|
@ -65,7 +65,6 @@ enum StoreKey<T> {
|
|||||||
|
|
||||||
// Video settings
|
// Video settings
|
||||||
loadOriginalVideo<bool>._(136),
|
loadOriginalVideo<bool>._(136),
|
||||||
manageLocalMediaAndroid<bool>._(137),
|
|
||||||
|
|
||||||
// Experimental stuff
|
// Experimental stuff
|
||||||
photoManagerCustomFilter<bool>._(1000);
|
photoManagerCustomFilter<bool>._(1000);
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
abstract interface class ILocalFilesManager {
|
|
||||||
Future<bool> moveToTrash(String fileName);
|
|
||||||
Future<bool> restoreFromTrash(String fileName);
|
|
||||||
Future<bool> requestManageStoragePermission();
|
|
||||||
}
|
|
@ -23,7 +23,6 @@ enum PendingAction {
|
|||||||
assetDelete,
|
assetDelete,
|
||||||
assetUploaded,
|
assetUploaded,
|
||||||
assetHidden,
|
assetHidden,
|
||||||
assetTrash,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class PendingChange {
|
class PendingChange {
|
||||||
@ -161,7 +160,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
socket.on('on_upload_success', _handleOnUploadSuccess);
|
socket.on('on_upload_success', _handleOnUploadSuccess);
|
||||||
socket.on('on_config_update', _handleOnConfigUpdate);
|
socket.on('on_config_update', _handleOnConfigUpdate);
|
||||||
socket.on('on_asset_delete', _handleOnAssetDelete);
|
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_restore', _handleServerUpdates);
|
||||||
socket.on('on_asset_update', _handleServerUpdates);
|
socket.on('on_asset_update', _handleServerUpdates);
|
||||||
socket.on('on_asset_stack_update', _handleServerUpdates);
|
socket.on('on_asset_stack_update', _handleServerUpdates);
|
||||||
@ -208,26 +207,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
_debounce.run(handlePendingChanges);
|
_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 {
|
Future<void> _handlePendingDeletes() async {
|
||||||
final deleteChanges = state.pendingChanges
|
final deleteChanges = state.pendingChanges
|
||||||
.where((c) => c.action == PendingAction.assetDelete)
|
.where((c) => c.action == PendingAction.assetDelete)
|
||||||
@ -288,7 +267,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
await _handlePendingUploaded();
|
await _handlePendingUploaded();
|
||||||
await _handlePendingDeletes();
|
await _handlePendingDeletes();
|
||||||
await _handlingPendingHidden();
|
await _handlingPendingHidden();
|
||||||
await _handlePendingTrashes();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleOnConfigUpdate(dynamic _) {
|
void _handleOnConfigUpdate(dynamic _) {
|
||||||
@ -307,10 +285,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
void _handleOnAssetDelete(dynamic data) =>
|
void _handleOnAssetDelete(dynamic data) =>
|
||||||
addPendingChange(PendingAction.assetDelete, data);
|
addPendingChange(PendingAction.assetDelete, data);
|
||||||
|
|
||||||
void _handleOnAssetTrash(dynamic data) {
|
|
||||||
addPendingChange(PendingAction.assetTrash, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _handleOnAssetHidden(dynamic data) =>
|
void _handleOnAssetHidden(dynamic data) =>
|
||||||
addPendingChange(PendingAction.assetHidden, data);
|
addPendingChange(PendingAction.assetHidden, data);
|
||||||
|
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
@ -61,7 +61,6 @@ enum AppSettingsEnum<T> {
|
|||||||
0,
|
0,
|
||||||
),
|
),
|
||||||
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
advancedTroubleshooting<bool>(StoreKey.advancedTroubleshooting, null, false),
|
||||||
manageLocalMediaAndroid<bool>(StoreKey.manageLocalMediaAndroid, null, false),
|
|
||||||
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
|
logLevel<int>(StoreKey.logLevel, null, 5), // Level.INFO = 5
|
||||||
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
preferRemoteImage<bool>(StoreKey.preferRemoteImage, null, false),
|
||||||
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
loopVideo<bool>(StoreKey.loopVideo, "loopVideo", true),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.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/album_media.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
import 'package:immich_mobile/interfaces/asset.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/etag.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.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner_api.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/exif.provider.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/user.provider.dart';
|
||||||
import 'package:immich_mobile/repositories/album.repository.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/album_media.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/asset.repository.dart';
|
import 'package:immich_mobile/repositories/asset.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/etag.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.repository.dart';
|
||||||
import 'package:immich_mobile/repositories/partner_api.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/entity.service.dart';
|
||||||
import 'package:immich_mobile/services/hash.service.dart';
|
import 'package:immich_mobile/services/hash.service.dart';
|
||||||
import 'package:immich_mobile/utils/async_mutex.dart';
|
import 'package:immich_mobile/utils/async_mutex.dart';
|
||||||
@ -53,8 +48,6 @@ final syncServiceProvider = Provider(
|
|||||||
ref.watch(userRepositoryProvider),
|
ref.watch(userRepositoryProvider),
|
||||||
ref.watch(userServiceProvider),
|
ref.watch(userServiceProvider),
|
||||||
ref.watch(etagRepositoryProvider),
|
ref.watch(etagRepositoryProvider),
|
||||||
ref.watch(appSettingsServiceProvider),
|
|
||||||
ref.watch(localFilesManagerRepositoryProvider),
|
|
||||||
ref.watch(partnerApiRepositoryProvider),
|
ref.watch(partnerApiRepositoryProvider),
|
||||||
ref.watch(userApiRepositoryProvider),
|
ref.watch(userApiRepositoryProvider),
|
||||||
),
|
),
|
||||||
@ -76,8 +69,6 @@ class SyncService {
|
|||||||
final IUserApiRepository _userApiRepository;
|
final IUserApiRepository _userApiRepository;
|
||||||
final AsyncMutex _lock = AsyncMutex();
|
final AsyncMutex _lock = AsyncMutex();
|
||||||
final Logger _log = Logger('SyncService');
|
final Logger _log = Logger('SyncService');
|
||||||
final AppSettingsService _appSettingsService;
|
|
||||||
final ILocalFilesManager _localFilesManager;
|
|
||||||
|
|
||||||
SyncService(
|
SyncService(
|
||||||
this._hashService,
|
this._hashService,
|
||||||
@ -91,8 +82,6 @@ class SyncService {
|
|||||||
this._userRepository,
|
this._userRepository,
|
||||||
this._userService,
|
this._userService,
|
||||||
this._eTagRepository,
|
this._eTagRepository,
|
||||||
this._appSettingsService,
|
|
||||||
this._localFilesManager,
|
|
||||||
this._partnerApiRepository,
|
this._partnerApiRepository,
|
||||||
this._userApiRepository,
|
this._userApiRepository,
|
||||||
);
|
);
|
||||||
@ -249,19 +238,8 @@ class SyncService {
|
|||||||
return null;
|
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
|
/// 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 {
|
return _assetRepository.transaction(() async {
|
||||||
await _assetRepository.deleteAllByRemoteId(
|
await _assetRepository.deleteAllByRemoteId(
|
||||||
idsToDelete,
|
idsToDelete,
|
||||||
@ -271,12 +249,6 @@ class SyncService {
|
|||||||
idsToDelete,
|
idsToDelete,
|
||||||
state: AssetState.merged,
|
state: AssetState.merged,
|
||||||
);
|
);
|
||||||
if (Platform.isAndroid &&
|
|
||||||
_appSettingsService.getSetting<bool>(
|
|
||||||
AppSettingsEnum.manageLocalMediaAndroid,
|
|
||||||
)) {
|
|
||||||
await _moveToTrashMatchedAssets(idsToDelete);
|
|
||||||
}
|
|
||||||
if (merged.isEmpty) return;
|
if (merged.isEmpty) return;
|
||||||
for (final Asset asset in merged) {
|
for (final Asset asset in merged) {
|
||||||
asset.remoteId = null;
|
asset.remoteId = null;
|
||||||
@ -818,27 +790,10 @@ class SyncService {
|
|||||||
return (existing, toUpsert);
|
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)
|
/// Inserts or updates the assets in the database with their ExifInfo (if any)
|
||||||
Future<void> upsertAssetsWithExif(List<Asset> assets) async {
|
Future<void> upsertAssetsWithExif(List<Asset> assets) async {
|
||||||
if (assets.isEmpty) return;
|
if (assets.isEmpty) return;
|
||||||
|
|
||||||
if (Platform.isAndroid &&
|
|
||||||
_appSettingsService.getSetting<bool>(
|
|
||||||
AppSettingsEnum.manageLocalMediaAndroid,
|
|
||||||
)) {
|
|
||||||
_toggleTrashStatusForAssets(assets);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await _assetRepository.transaction(() async {
|
await _assetRepository.transaction(() async {
|
||||||
await _assetRepository.updateAll(assets);
|
await _assetRepository.updateAll(assets);
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,13 +1,11 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
import 'package:flutter_hooks/flutter_hooks.dart' hide Store;
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/domain/services/log.service.dart';
|
import 'package:immich_mobile/domain/services/log.service.dart';
|
||||||
import 'package:immich_mobile/providers/user.provider.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/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/utils/http_ssl_cert_override.dart';
|
import 'package:immich_mobile/utils/http_ssl_cert_override.dart';
|
||||||
@ -27,8 +25,6 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
|
|
||||||
final advancedTroubleshooting =
|
final advancedTroubleshooting =
|
||||||
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
useAppSettingsState(AppSettingsEnum.advancedTroubleshooting);
|
||||||
final manageLocalMediaAndroid =
|
|
||||||
useAppSettingsState(AppSettingsEnum.manageLocalMediaAndroid);
|
|
||||||
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
|
final levelId = useAppSettingsState(AppSettingsEnum.logLevel);
|
||||||
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
final preferRemote = useAppSettingsState(AppSettingsEnum.preferRemoteImage);
|
||||||
final allowSelfSignedSSLCert =
|
final allowSelfSignedSSLCert =
|
||||||
@ -44,16 +40,6 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
LogService.I.setlogLevel(Level.LEVELS[levelId.value].toLogLevel()),
|
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 = [
|
final advancedSettings = [
|
||||||
SettingsSwitchListTile(
|
SettingsSwitchListTile(
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@ -61,29 +47,6 @@ class AdvancedSettings extends HookConsumerWidget {
|
|||||||
title: "advanced_settings_troubleshooting_title".tr(),
|
title: "advanced_settings_troubleshooting_title".tr(),
|
||||||
subtitle: "advanced_settings_troubleshooting_subtitle".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(
|
SettingsSliderListTile(
|
||||||
text: "advanced_settings_log_level_title".tr(args: [logLevel]),
|
text: "advanced_settings_log_level_title".tr(args: [logLevel]),
|
||||||
valueNotifier: levelId,
|
valueNotifier: levelId,
|
||||||
|
2
mobile/openapi/README.md
generated
2
mobile/openapi/README.md
generated
@ -3,7 +3,7 @@ Immich API
|
|||||||
|
|
||||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
|
||||||
|
|
||||||
- API version: 1.132.0
|
- API version: 1.132.1
|
||||||
- Generator version: 7.8.0
|
- Generator version: 7.8.0
|
||||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ name: immich_mobile
|
|||||||
description: Immich - selfhosted backup media file on mobile phone
|
description: Immich - selfhosted backup media file on mobile phone
|
||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.132.0+194
|
version: 1.132.1+195
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.3.0 <4.0.0'
|
sdk: '>=3.3.0 <4.0.0'
|
||||||
|
@ -60,9 +60,6 @@ void main() {
|
|||||||
final MockAlbumMediaRepository albumMediaRepository =
|
final MockAlbumMediaRepository albumMediaRepository =
|
||||||
MockAlbumMediaRepository();
|
MockAlbumMediaRepository();
|
||||||
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
|
final MockAlbumApiRepository albumApiRepository = MockAlbumApiRepository();
|
||||||
final MockAppSettingService appSettingService = MockAppSettingService();
|
|
||||||
final MockLocalFilesManagerRepository localFilesManagerRepository =
|
|
||||||
MockLocalFilesManagerRepository();
|
|
||||||
final MockPartnerApiRepository partnerApiRepository =
|
final MockPartnerApiRepository partnerApiRepository =
|
||||||
MockPartnerApiRepository();
|
MockPartnerApiRepository();
|
||||||
final MockUserApiRepository userApiRepository = MockUserApiRepository();
|
final MockUserApiRepository userApiRepository = MockUserApiRepository();
|
||||||
@ -109,8 +106,6 @@ void main() {
|
|||||||
userRepository,
|
userRepository,
|
||||||
userService,
|
userService,
|
||||||
eTagRepository,
|
eTagRepository,
|
||||||
appSettingService,
|
|
||||||
localFilesManagerRepository,
|
|
||||||
partnerApiRepository,
|
partnerApiRepository,
|
||||||
userApiRepository,
|
userApiRepository,
|
||||||
);
|
);
|
||||||
|
@ -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/backup_album.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
import 'package:immich_mobile/interfaces/etag.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/file_media.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.interface.dart';
|
||||||
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
import 'package:immich_mobile/interfaces/partner_api.interface.dart';
|
||||||
import 'package:mocktail/mocktail.dart';
|
import 'package:mocktail/mocktail.dart';
|
||||||
@ -42,9 +41,6 @@ class MockAuthApiRepository extends Mock implements IAuthApiRepository {}
|
|||||||
|
|
||||||
class MockAuthRepository extends Mock implements IAuthRepository {}
|
class MockAuthRepository extends Mock implements IAuthRepository {}
|
||||||
|
|
||||||
class MockPartnerRepository extends Mock implements IPartnerRepository {}
|
|
||||||
|
|
||||||
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
|
class MockPartnerApiRepository extends Mock implements IPartnerApiRepository {}
|
||||||
|
|
||||||
class MockLocalFilesManagerRepository extends Mock
|
class MockPartnerRepository extends Mock implements IPartnerRepository {}
|
||||||
implements ILocalFilesManager {}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'package:immich_mobile/services/album.service.dart';
|
import 'package:immich_mobile/services/album.service.dart';
|
||||||
import 'package:immich_mobile/services/api.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/background.service.dart';
|
||||||
import 'package:immich_mobile/services/backup.service.dart';
|
import 'package:immich_mobile/services/backup.service.dart';
|
||||||
import 'package:immich_mobile/services/entity.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 MockSearchApi extends Mock implements SearchApi {}
|
||||||
|
|
||||||
class MockAppSettingService extends Mock implements AppSettingsService {}
|
|
||||||
|
|
||||||
class MockBackgroundService extends Mock implements BackgroundService {}
|
class MockBackgroundService extends Mock implements BackgroundService {}
|
||||||
|
@ -7656,7 +7656,7 @@
|
|||||||
"info": {
|
"info": {
|
||||||
"title": "Immich",
|
"title": "Immich",
|
||||||
"description": "Immich API",
|
"description": "Immich API",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"contact": {}
|
"contact": {}
|
||||||
},
|
},
|
||||||
"tags": [],
|
"tags": [],
|
||||||
|
4
open-api/typescript-sdk/package-lock.json
generated
4
open-api/typescript-sdk/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"description": "Auto-generated TypeScript SDK for the Immich API",
|
"description": "Auto-generated TypeScript SDK for the Immich API",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./build/index.js",
|
"main": "./build/index.js",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Immich
|
* Immich
|
||||||
* 1.132.0
|
* 1.132.1
|
||||||
* DO NOT MODIFY - This file has been generated using oazapfts.
|
* DO NOT MODIFY - This file has been generated using oazapfts.
|
||||||
* See https://www.npmjs.com/package/oazapfts
|
* See https://www.npmjs.com/package/oazapfts
|
||||||
*/
|
*/
|
||||||
|
@ -6,14 +6,14 @@ WORKDIR /usr/src/app
|
|||||||
COPY server/package.json server/package-lock.json ./
|
COPY server/package.json server/package-lock.json ./
|
||||||
COPY server/patches ./patches
|
COPY server/patches ./patches
|
||||||
RUN npm ci && \
|
RUN npm ci && \
|
||||||
# exiftool-vendored.pl, sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
|
# exiftool-vendored.pl, sharp-linux-x64 and sharp-linux-arm64 are the only ones we need
|
||||||
# they're marked as optional dependencies, so we need to copy them manually after pruning
|
# they're marked as optional dependencies, so we need to copy them manually after pruning
|
||||||
rm -rf node_modules/@img/sharp-libvips* && \
|
rm -rf node_modules/@img/sharp-libvips* && \
|
||||||
rm -rf node_modules/@img/sharp-linuxmusl-x64
|
rm -rf node_modules/@img/sharp-linuxmusl-x64
|
||||||
ENV PATH="${PATH}:/usr/src/app/bin" \
|
ENV PATH="${PATH}:/usr/src/app/bin" \
|
||||||
IMMICH_ENV=development \
|
IMMICH_ENV=development \
|
||||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||||
NVIDIA_VISIBLE_DEVICES=all
|
NVIDIA_VISIBLE_DEVICES=all
|
||||||
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
||||||
|
|
||||||
|
|
||||||
@ -47,8 +47,8 @@ FROM ghcr.io/immich-app/base-server-prod:202504081114@sha256:8353bcbdb4e6579300a
|
|||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production \
|
ENV NODE_ENV=production \
|
||||||
NVIDIA_DRIVER_CAPABILITIES=all \
|
NVIDIA_DRIVER_CAPABILITIES=all \
|
||||||
NVIDIA_VISIBLE_DEVICES=all
|
NVIDIA_VISIBLE_DEVICES=all
|
||||||
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
COPY --from=prod /usr/src/app/node_modules ./node_modules
|
||||||
COPY --from=prod /usr/src/app/dist ./dist
|
COPY --from=prod /usr/src/app/dist ./dist
|
||||||
COPY --from=prod /usr/src/app/bin ./bin
|
COPY --from=prod /usr/src/app/bin ./bin
|
||||||
|
1261
server/package-lock.json
generated
1261
server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich",
|
"name": "immich",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -88,7 +88,7 @@
|
|||||||
"sanitize-filename": "^1.6.3",
|
"sanitize-filename": "^1.6.3",
|
||||||
"sanitize-html": "^2.14.0",
|
"sanitize-html": "^2.14.0",
|
||||||
"semver": "^7.6.2",
|
"semver": "^7.6.2",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.34.0",
|
||||||
"sirv": "^3.0.0",
|
"sirv": "^3.0.0",
|
||||||
"tailwindcss-preset-email": "^1.3.2",
|
"tailwindcss-preset-email": "^1.3.2",
|
||||||
"thumbhash": "^0.1.1",
|
"thumbhash": "^0.1.1",
|
||||||
@ -132,7 +132,8 @@
|
|||||||
"globals": "^16.0.0",
|
"globals": "^16.0.0",
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"mock-fs": "^5.2.0",
|
"mock-fs": "^5.2.0",
|
||||||
"node-addon-api": "^8.3.0",
|
"node-addon-api": "^8.3.1",
|
||||||
|
"node-gyp": "^11.2.0",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
"pngjs": "^7.0.0",
|
"pngjs": "^7.0.0",
|
||||||
"prettier": "^3.0.2",
|
"prettier": "^3.0.2",
|
||||||
@ -152,5 +153,8 @@
|
|||||||
},
|
},
|
||||||
"volta": {
|
"volta": {
|
||||||
"node": "22.14.0"
|
"node": "22.14.0"
|
||||||
|
},
|
||||||
|
"overrides": {
|
||||||
|
"sharp": "^0.34.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
6
web/package-lock.json
generated
6
web/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||||
@ -82,7 +82,7 @@
|
|||||||
},
|
},
|
||||||
"../open-api/typescript-sdk": {
|
"../open-api/typescript-sdk": {
|
||||||
"name": "@immich/sdk",
|
"name": "@immich/sdk",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@oazapfts/runtime": "^1.0.2"
|
"@oazapfts/runtime": "^1.0.2"
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "immich-web",
|
"name": "immich-web",
|
||||||
"version": "1.132.0",
|
"version": "1.132.1",
|
||||||
"license": "GNU Affero General Public License version 3",
|
"license": "GNU Affero General Public License version 3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user