Procházet zdrojové kódy

Add unified storage location setting

Currently only using it as a replacement for the downloads location.
arkon před 1 rokem
rodič
revize
695813ef7d

+ 45 - 0
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt

@@ -1,5 +1,6 @@
 package eu.kanade.presentation.more.settings.screen
 
+import android.content.ActivityNotFoundException
 import android.content.Context
 import android.content.Intent
 import android.net.Uri
@@ -26,8 +27,11 @@ import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.core.net.toUri
 import cafe.adriel.voyager.navigator.LocalNavigator
 import cafe.adriel.voyager.navigator.currentOrThrow
+import com.hippo.unifile.UniFile
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.screen.data.CreateBackupScreen
 import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
@@ -49,6 +53,7 @@ import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.backup.service.BackupPreferences
 import tachiyomi.domain.library.service.LibraryPreferences
+import tachiyomi.domain.storage.service.StoragePreferences
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.i18n.stringResource
 import tachiyomi.presentation.core.util.collectAsState
@@ -64,15 +69,55 @@ object SettingsDataScreen : SearchableSettings {
     @Composable
     override fun getPreferences(): List<Preference> {
         val backupPreferences = Injekt.get<BackupPreferences>()
+        val storagePreferences = Injekt.get<StoragePreferences>()
 
         PermissionRequestHelper.requestStoragePermission()
 
         return listOf(
+            getStorageLocationPref(storagePreferences = storagePreferences),
+            Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
+
             getBackupAndRestoreGroup(backupPreferences = backupPreferences),
             getDataGroup(),
         )
     }
 
+    @Composable
+    private fun getStorageLocationPref(
+        storagePreferences: StoragePreferences,
+    ): Preference.PreferenceItem.TextPreference {
+        val context = LocalContext.current
+        val storageDirPref = storagePreferences.baseStorageDirectory()
+        val storageDir by storageDirPref.collectAsState()
+        val pickStorageLocation = rememberLauncherForActivityResult(
+            contract = ActivityResultContracts.OpenDocumentTree(),
+        ) { uri ->
+            if (uri != null) {
+                val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
+                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+
+                context.contentResolver.takePersistableUriPermission(uri, flags)
+
+                val file = UniFile.fromUri(context, uri)
+                storageDirPref.set(file.uri.toString())
+            }
+        }
+
+        return Preference.PreferenceItem.TextPreference(
+            title = stringResource(MR.strings.pref_storage_location),
+            subtitle = remember(storageDir) {
+                (UniFile.fromUri(context, storageDir.toUri())?.filePath)
+            } ?: stringResource(MR.strings.invalid_location, storageDir),
+            onClick = {
+                try {
+                    pickStorageLocation.launch(null)
+                } catch (e: ActivityNotFoundException) {
+                    context.toast(MR.strings.file_picker_error)
+                }
+            },
+        )
+    }
+
     @Composable
     private fun getBackupAndRestoreGroup(backupPreferences: BackupPreferences): Preference.PreferenceGroup {
         val context = LocalContext.current

+ 0 - 70
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt

@@ -1,9 +1,5 @@
 package eu.kanade.presentation.more.settings.screen
 
-import android.content.Intent
-import android.os.Environment
-import androidx.activity.compose.rememberLauncherForActivityResult
-import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.ReadOnlyComposable
 import androidx.compose.runtime.collectAsState
@@ -12,10 +8,7 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.util.fastMap
-import androidx.core.net.toUri
-import com.hippo.unifile.UniFile
 import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.widget.TriStateListDialog
@@ -29,7 +22,6 @@ import tachiyomi.presentation.core.i18n.stringResource
 import tachiyomi.presentation.core.util.collectAsState
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
-import java.io.File
 
 object SettingsDownloadScreen : SearchableSettings {
 
@@ -44,7 +36,6 @@ object SettingsDownloadScreen : SearchableSettings {
 
         val downloadPreferences = remember { Injekt.get<DownloadPreferences>() }
         return listOf(
-            getDownloadLocationPreference(downloadPreferences = downloadPreferences),
             Preference.PreferenceItem.SwitchPreference(
                 pref = downloadPreferences.downloadOnlyOverWifi(),
                 title = stringResource(MR.strings.connected_to_wifi),
@@ -70,67 +61,6 @@ object SettingsDownloadScreen : SearchableSettings {
         )
     }
 
-    @Composable
-    private fun getDownloadLocationPreference(
-        downloadPreferences: DownloadPreferences,
-    ): Preference.PreferenceItem.ListPreference<String> {
-        val context = LocalContext.current
-        val currentDirPref = downloadPreferences.downloadsDirectory()
-        val currentDir by currentDirPref.collectAsState()
-
-        val pickLocation = rememberLauncherForActivityResult(
-            contract = ActivityResultContracts.OpenDocumentTree(),
-        ) { uri ->
-            if (uri != null) {
-                val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
-                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
-
-                context.contentResolver.takePersistableUriPermission(uri, flags)
-
-                val file = UniFile.fromUri(context, uri)
-                currentDirPref.set(file.uri.toString())
-            }
-        }
-
-        val defaultDirPair = rememberDefaultDownloadDir()
-        val customDirEntryKey = currentDir.takeIf { it != defaultDirPair.first } ?: "custom"
-
-        return Preference.PreferenceItem.ListPreference(
-            pref = currentDirPref,
-            title = stringResource(MR.strings.pref_download_directory),
-            subtitleProvider = { value, _ ->
-                remember(value) {
-                    UniFile.fromUri(context, value.toUri())?.filePath
-                } ?: stringResource(MR.strings.invalid_location, value)
-            },
-            entries = mapOf(
-                defaultDirPair,
-                customDirEntryKey to stringResource(MR.strings.custom_dir),
-            ),
-            onValueChanged = {
-                val default = it == defaultDirPair.first
-                if (!default) {
-                    pickLocation.launch(null)
-                }
-                default // Don't update when non-default chosen
-            },
-        )
-    }
-
-    @Composable
-    private fun rememberDefaultDownloadDir(): Pair<String, String> {
-        val appName = stringResource(MR.strings.app_name)
-        return remember {
-            val file = UniFile.fromFile(
-                File(
-                    "${Environment.getExternalStorageDirectory().absolutePath}${File.separator}$appName",
-                    "downloads",
-                ),
-            )!!
-            file.uri.toString() to file.filePath!!
-        }
-    }
-
     @Composable
     private fun getDeleteChaptersGroup(
         downloadPreferences: DownloadPreferences,

+ 2 - 2
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreator.kt

@@ -92,8 +92,8 @@ class BackupCreator(
             file = (
                 if (isAutoBackup) {
                     // Get dir of file and create
-                    var dir = UniFile.fromUri(context, uri)
-                    dir = dir.createDirectory("automatic")
+                    val dir = UniFile.fromUri(context, uri)
+                        .createDirectory("automatic")
 
                     // Delete older backups
                     dir.listFiles { _, filename -> Backup.filenameRegex.matches(filename) }

+ 5 - 5
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt

@@ -44,9 +44,9 @@ import tachiyomi.core.util.lang.launchIO
 import tachiyomi.core.util.lang.launchNonCancellable
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.chapter.model.Chapter
-import tachiyomi.domain.download.service.DownloadPreferences
 import tachiyomi.domain.manga.model.Manga
 import tachiyomi.domain.source.service.SourceManager
+import tachiyomi.domain.storage.service.StoragePreferences
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
 import java.io.File
@@ -64,7 +64,7 @@ class DownloadCache(
     private val provider: DownloadProvider = Injekt.get(),
     private val sourceManager: SourceManager = Injekt.get(),
     private val extensionManager: ExtensionManager = Injekt.get(),
-    private val downloadPreferences: DownloadPreferences = Injekt.get(),
+    private val storagePreferences: StoragePreferences = Injekt.get(),
 ) {
 
     private val scope = CoroutineScope(Dispatchers.IO)
@@ -98,7 +98,7 @@ class DownloadCache(
     private var rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
 
     init {
-        downloadPreferences.downloadsDirectory().changes()
+        storagePreferences.baseStorageDirectory().changes()
             .onEach {
                 rootDownloadsDir = RootDirectory(getDirectoryFromPreference())
                 invalidateCache()
@@ -297,8 +297,8 @@ class DownloadCache(
      * Returns the downloads directory from the user's preferences.
      */
     private fun getDirectoryFromPreference(): UniFile {
-        val dir = downloadPreferences.downloadsDirectory().get()
-        return UniFile.fromUri(context, dir.toUri())
+        return UniFile.fromUri(context, storagePreferences.baseStorageDirectory().get().toUri())
+            .createDirectory(StoragePreferences.DOWNLOADS_DIR)
     }
 
     /**

+ 15 - 9
app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadProvider.kt

@@ -12,8 +12,8 @@ import logcat.LogPriority
 import tachiyomi.core.i18n.stringResource
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.chapter.model.Chapter
-import tachiyomi.domain.download.service.DownloadPreferences
 import tachiyomi.domain.manga.model.Manga
+import tachiyomi.domain.storage.service.StoragePreferences
 import tachiyomi.i18n.MR
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -26,7 +26,7 @@ import uy.kohesive.injekt.api.get
  */
 class DownloadProvider(
     private val context: Context,
-    downloadPreferences: DownloadPreferences = Injekt.get(),
+    private val storagePreferences: StoragePreferences = Injekt.get(),
 ) {
 
     private val scope = MainScope()
@@ -34,18 +34,24 @@ class DownloadProvider(
     /**
      * The root directory for downloads.
      */
-    private var downloadsDir = downloadPreferences.downloadsDirectory().get().let {
-        val dir = UniFile.fromUri(context, it.toUri())
-        DiskUtil.createNoMediaFile(dir, context)
-        dir
-    }
+    private var downloadsDir = setDownloadsLocation()
 
     init {
-        downloadPreferences.downloadsDirectory().changes()
-            .onEach { downloadsDir = UniFile.fromUri(context, it.toUri()) }
+        storagePreferences.baseStorageDirectory().changes()
+            .onEach { downloadsDir = setDownloadsLocation() }
             .launchIn(scope)
     }
 
+    private fun setDownloadsLocation(): UniFile {
+        return storagePreferences.baseStorageDirectory().get().let {
+            val dir = UniFile.fromUri(context, it.toUri())
+                .createDirectory(StoragePreferences.DOWNLOADS_DIR)
+            DiskUtil.createNoMediaFile(dir, context)
+            logcat { "downloadsDir: ${dir.filePath}" }
+            dir
+        }
+    }
+
     /**
      * Returns the download directory for a manga. For internal use only.
      *

+ 11 - 7
app/src/main/java/eu/kanade/tachiyomi/di/PreferenceModule.kt

@@ -12,10 +12,11 @@ import eu.kanade.tachiyomi.util.system.isDevFlavor
 import tachiyomi.core.preference.AndroidPreferenceStore
 import tachiyomi.core.preference.PreferenceStore
 import tachiyomi.core.provider.AndroidBackupFolderProvider
-import tachiyomi.core.provider.AndroidDownloadFolderProvider
+import tachiyomi.core.provider.AndroidStorageFolderProvider
 import tachiyomi.domain.backup.service.BackupPreferences
 import tachiyomi.domain.download.service.DownloadPreferences
 import tachiyomi.domain.library.service.LibraryPreferences
+import tachiyomi.domain.storage.service.StoragePreferences
 import uy.kohesive.injekt.api.InjektModule
 import uy.kohesive.injekt.api.InjektRegistrar
 import uy.kohesive.injekt.api.addSingletonFactory
@@ -49,20 +50,23 @@ class PreferenceModule(val app: Application) : InjektModule {
             TrackPreferences(get())
         }
         addSingletonFactory {
-            AndroidDownloadFolderProvider(app)
+            DownloadPreferences(get())
         }
         addSingletonFactory {
-            DownloadPreferences(
-                folderProvider = get<AndroidDownloadFolderProvider>(),
+            AndroidBackupFolderProvider(app)
+        }
+        addSingletonFactory {
+            BackupPreferences(
+                folderProvider = get<AndroidBackupFolderProvider>(),
                 preferenceStore = get(),
             )
         }
         addSingletonFactory {
-            AndroidBackupFolderProvider(app)
+            AndroidStorageFolderProvider(app)
         }
         addSingletonFactory {
-            BackupPreferences(
-                folderProvider = get<AndroidBackupFolderProvider>(),
+            StoragePreferences(
+                folderProvider = get<AndroidStorageFolderProvider>(),
                 preferenceStore = get(),
             )
         }

+ 2 - 3
core/src/main/java/tachiyomi/core/provider/AndroidDownloadFolderProvider.kt → core/src/main/java/tachiyomi/core/provider/AndroidStorageFolderProvider.kt

@@ -7,15 +7,14 @@ import tachiyomi.core.i18n.stringResource
 import tachiyomi.i18n.MR
 import java.io.File
 
-class AndroidDownloadFolderProvider(
-    val context: Context,
+class AndroidStorageFolderProvider(
+    private val context: Context,
 ) : FolderProvider {
 
     override fun directory(): File {
         return File(
             Environment.getExternalStorageDirectory().absolutePath + File.separator +
                 context.stringResource(MR.strings.app_name),
-            "downloads",
         )
     }
 

+ 0 - 7
domain/src/main/java/tachiyomi/domain/download/service/DownloadPreferences.kt

@@ -1,18 +1,11 @@
 package tachiyomi.domain.download.service
 
 import tachiyomi.core.preference.PreferenceStore
-import tachiyomi.core.provider.FolderProvider
 
 class DownloadPreferences(
-    private val folderProvider: FolderProvider,
     private val preferenceStore: PreferenceStore,
 ) {
 
-    fun downloadsDirectory() = preferenceStore.getString(
-        "download_directory",
-        folderProvider.path(),
-    )
-
     fun downloadOnlyOverWifi() = preferenceStore.getBoolean(
         "pref_download_only_over_wifi_key",
         true,

+ 16 - 0
domain/src/main/java/tachiyomi/domain/storage/service/StoragePreferences.kt

@@ -0,0 +1,16 @@
+package tachiyomi.domain.storage.service
+
+import tachiyomi.core.preference.PreferenceStore
+import tachiyomi.core.provider.FolderProvider
+
+class StoragePreferences(
+    private val folderProvider: FolderProvider,
+    private val preferenceStore: PreferenceStore,
+) {
+
+    fun baseStorageDirectory() = preferenceStore.getString("storage_dir", folderProvider.path())
+
+    companion object {
+        const val DOWNLOADS_DIR = "downloads"
+    }
+}

+ 2 - 2
i18n/src/commonMain/resources/MR/base/strings.xml

@@ -426,13 +426,11 @@
     <string name="pref_lowest">Lowest</string>
 
       <!-- Downloads section -->
-    <string name="pref_download_directory">Download location</string>
     <string name="pref_category_delete_chapters">Delete chapters</string>
     <string name="pref_remove_after_marked_as_read">After manually marked as read</string>
     <string name="pref_remove_after_read">After reading automatically delete</string>
     <string name="pref_remove_bookmarked_chapters">Allow deleting bookmarked chapters</string>
     <string name="pref_remove_exclude_categories">Excluded categories</string>
-    <string name="custom_dir">Custom location</string>
     <string name="invalid_location">Invalid location: %s</string>
     <string name="disabled">Disabled</string>
     <string name="last_read_chapter">Last read chapter</string>
@@ -465,6 +463,8 @@
     <string name="pref_hide_in_library_items">Hide entries already in library</string>
 
       <!-- Data and storage section -->
+    <string name="pref_storage_location">Storage location</string>
+    <string name="pref_storage_location_info">Used for automatic backups, chapter downloads, and local source.</string>
     <string name="pref_create_backup">Create backup</string>
     <string name="pref_create_backup_summ">Can be used to restore current library</string>
     <string name="pref_restore_backup">Restore backup</string>