Add unified storage location setting
Currently only using it as a replacement for the downloads location.
This commit is contained in:
parent
e3b70ca08d
commit
695813ef7d
10 changed files with 99 additions and 106 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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,13 +50,7 @@ class PreferenceModule(val app: Application) : InjektModule {
|
|||
TrackPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
AndroidDownloadFolderProvider(app)
|
||||
}
|
||||
addSingletonFactory {
|
||||
DownloadPreferences(
|
||||
folderProvider = get<AndroidDownloadFolderProvider>(),
|
||||
preferenceStore = get(),
|
||||
)
|
||||
DownloadPreferences(get())
|
||||
}
|
||||
addSingletonFactory {
|
||||
AndroidBackupFolderProvider(app)
|
||||
|
@ -66,6 +61,15 @@ class PreferenceModule(val app: Application) : InjektModule {
|
|||
preferenceStore = get(),
|
||||
)
|
||||
}
|
||||
addSingletonFactory {
|
||||
AndroidStorageFolderProvider(app)
|
||||
}
|
||||
addSingletonFactory {
|
||||
StoragePreferences(
|
||||
folderProvider = get<AndroidStorageFolderProvider>(),
|
||||
preferenceStore = get(),
|
||||
)
|
||||
}
|
||||
addSingletonFactory {
|
||||
UiPreferences(get())
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
Reference in a new issue