Procházet zdrojové kódy

Clean up storage usage info

- Show bar representation of used/total space
- Handle all mounted storages
- Also included a bunch of unrelated immutables changes, sorry
arkon před 1 rokem
rodič
revize
f31bc47757
20 změnil soubory, kde provedl 301 přidání a 128 odebrání
  1. 14 9
      app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt
  2. 11 7
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt
  3. 12 6
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt
  4. 3 2
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt
  5. 20 37
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt
  6. 18 11
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDownloadScreen.kt
  7. 12 9
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt
  8. 32 19
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt
  9. 7 3
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt
  10. 14 10
      app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt
  11. 2 1
      app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt
  12. 117 0
      app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt
  13. 8 6
      app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt
  14. 2 1
      app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt
  15. 2 1
      app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt
  16. 4 4
      app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt
  17. 1 1
      app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt
  18. 19 0
      core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt
  19. 1 0
      i18n/src/commonMain/resources/MR/base/strings.xml
  20. 2 1
      presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt

+ 14 - 9
app/src/main/java/eu/kanade/presentation/more/settings/Preference.kt

@@ -4,6 +4,8 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.graphics.vector.ImageVector
 import eu.kanade.tachiyomi.data.track.Tracker
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.ImmutableMap
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.i18n.stringResource
 import tachiyomi.core.preference.Preference as PreferenceData
@@ -64,20 +66,20 @@ sealed class Preference {
             val pref: PreferenceData<T>,
             override val title: String,
             override val subtitle: String? = "%s",
-            val subtitleProvider: @Composable (value: T, entries: Map<T, String>) -> String? =
+            val subtitleProvider: @Composable (value: T, entries: ImmutableMap<T, String>) -> String? =
                 { v, e -> subtitle?.format(e[v]) },
             override val icon: ImageVector? = null,
             override val enabled: Boolean = true,
             override val onValueChanged: suspend (newValue: T) -> Boolean = { true },
 
-            val entries: Map<T, String>,
+            val entries: ImmutableMap<T, String>,
         ) : PreferenceItem<T>() {
             internal fun internalSet(newValue: Any) = pref.set(newValue as T)
             internal suspend fun internalOnValueChanged(newValue: Any) = onValueChanged(newValue as T)
 
             @Composable
-            internal fun internalSubtitleProvider(value: Any?, entries: Map<out Any?, String>) =
-                subtitleProvider(value as T, entries as Map<T, String>)
+            internal fun internalSubtitleProvider(value: Any?, entries: ImmutableMap<out Any?, String>) =
+                subtitleProvider(value as T, entries as ImmutableMap<T, String>)
         }
 
         /**
@@ -87,13 +89,13 @@ sealed class Preference {
             val value: String,
             override val title: String,
             override val subtitle: String? = "%s",
-            val subtitleProvider: @Composable (value: String, entries: Map<String, String>) -> String? =
+            val subtitleProvider: @Composable (value: String, entries: ImmutableMap<String, String>) -> String? =
                 { v, e -> subtitle?.format(e[v]) },
             override val icon: ImageVector? = null,
             override val enabled: Boolean = true,
             override val onValueChanged: suspend (newValue: String) -> Boolean = { true },
 
-            val entries: Map<String, String>,
+            val entries: ImmutableMap<String, String>,
         ) : PreferenceItem<String>()
 
         /**
@@ -104,7 +106,10 @@ sealed class Preference {
             val pref: PreferenceData<Set<String>>,
             override val title: String,
             override val subtitle: String? = "%s",
-            val subtitleProvider: @Composable (value: Set<String>, entries: Map<String, String>) -> String? = { v, e ->
+            val subtitleProvider: @Composable (
+                value: Set<String>,
+                entries: ImmutableMap<String, String>,
+            ) -> String? = { v, e ->
                 val combined = remember(v) {
                     v.map { e[it] }
                         .takeIf { it.isNotEmpty() }
@@ -116,7 +121,7 @@ sealed class Preference {
             override val enabled: Boolean = true,
             override val onValueChanged: suspend (newValue: Set<String>) -> Boolean = { true },
 
-            val entries: Map<String, String>,
+            val entries: ImmutableMap<String, String>,
         ) : PreferenceItem<Set<String>>()
 
         /**
@@ -170,6 +175,6 @@ sealed class Preference {
         override val title: String,
         override val enabled: Boolean = true,
 
-        val preferenceItems: List<PreferenceItem<out Any>>,
+        val preferenceItems: ImmutableList<PreferenceItem<out Any>>,
     ) : Preference()
 }

+ 11 - 7
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAdvancedScreen.kt

@@ -51,6 +51,9 @@ import eu.kanade.tachiyomi.util.system.isShizukuInstalled
 import eu.kanade.tachiyomi.util.system.powerManager
 import eu.kanade.tachiyomi.util.system.setDefaultSettings
 import eu.kanade.tachiyomi.util.system.toast
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.collections.immutable.toImmutableMap
 import kotlinx.coroutines.launch
 import logcat.LogPriority
 import okhttp3.Headers
@@ -149,7 +152,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_background_activity),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.pref_disable_battery_optimization),
                     subtitle = stringResource(MR.strings.pref_disable_battery_optimization_summary),
@@ -188,7 +191,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_data),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.pref_invalidate_download_cache),
                     subtitle = stringResource(MR.strings.pref_invalidate_download_cache_summary),
@@ -218,7 +221,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_network),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.pref_clear_cookies),
                     onClick = {
@@ -249,7 +252,7 @@ object SettingsAdvancedScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = networkPreferences.dohProvider(),
                     title = stringResource(MR.strings.pref_dns_over_https),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         -1 to stringResource(MR.strings.disabled),
                         PREF_DOH_CLOUDFLARE to "Cloudflare",
                         PREF_DOH_GOOGLE to "Google",
@@ -302,7 +305,7 @@ object SettingsAdvancedScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_library),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.pref_refresh_library_covers),
                     onClick = { MetadataUpdateJob.startNow(context) },
@@ -362,12 +365,13 @@ object SettingsAdvancedScreen : SearchableSettings {
         }
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_extensions),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = extensionInstallerPref,
                     title = stringResource(MR.strings.ext_installer_pref),
                     entries = extensionInstallerPref.entries
-                        .associateWith { stringResource(it.titleRes) },
+                        .associateWith { stringResource(it.titleRes) }
+                        .toImmutableMap(),
                     onValueChanged = {
                         if (it == BasePreferences.ExtensionInstaller.SHIZUKU &&
                             !context.isShizukuInstalled

+ 12 - 6
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsAppearanceScreen.kt

@@ -24,6 +24,9 @@ import eu.kanade.presentation.more.settings.widget.AppThemePreferenceWidget
 import eu.kanade.tachiyomi.R
 import eu.kanade.tachiyomi.util.system.LocaleHelper
 import eu.kanade.tachiyomi.util.system.toast
+import kotlinx.collections.immutable.ImmutableMap
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableMap
 import org.xmlpull.v1.XmlPullParser
 import tachiyomi.core.i18n.stringResource
 import tachiyomi.i18n.MR
@@ -66,7 +69,7 @@ object SettingsAppearanceScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_theme),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.CustomPreference(
                     title = stringResource(MR.strings.pref_app_theme),
                 ) {
@@ -127,7 +130,7 @@ object SettingsAppearanceScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_display),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.BasicListPreference(
                     value = currentLanguage,
                     title = stringResource(MR.strings.pref_app_language),
@@ -140,7 +143,9 @@ object SettingsAppearanceScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = uiPreferences.tabletUiMode(),
                     title = stringResource(MR.strings.pref_tablet_ui_mode),
-                    entries = TabletUiMode.entries.associateWith { stringResource(it.titleRes) },
+                    entries = TabletUiMode.entries
+                        .associateWith { stringResource(it.titleRes) }
+                        .toImmutableMap(),
                     onValueChanged = {
                         context.toast(MR.strings.requires_app_restart)
                         true
@@ -153,7 +158,8 @@ object SettingsAppearanceScreen : SearchableSettings {
                         .associateWith {
                             val formattedDate = UiPreferences.dateFormat(it).format(now)
                             "${it.ifEmpty { stringResource(MR.strings.label_default) }} ($formattedDate)"
-                        },
+                        }
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = uiPreferences.relativeTime(),
@@ -167,7 +173,7 @@ object SettingsAppearanceScreen : SearchableSettings {
             ),
         )
     }
-    private fun getLangs(context: Context): Map<String, String> {
+    private fun getLangs(context: Context): ImmutableMap<String, String> {
         val langs = mutableListOf<Pair<String, String>>()
         val parser = context.resources.getXml(R.xml.locales_config)
         var eventType = parser.eventType
@@ -189,7 +195,7 @@ object SettingsAppearanceScreen : SearchableSettings {
         langs.sortBy { it.second }
         langs.add(0, Pair("", context.stringResource(MR.strings.label_default)))
 
-        return langs.toMap()
+        return langs.toMap().toImmutableMap()
     }
 }
 

+ 3 - 2
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBrowseScreen.kt

@@ -8,6 +8,7 @@ import androidx.fragment.app.FragmentActivity
 import eu.kanade.domain.source.service.SourcePreferences
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
+import kotlinx.collections.immutable.persistentListOf
 import tachiyomi.core.i18n.stringResource
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.i18n.stringResource
@@ -27,7 +28,7 @@ object SettingsBrowseScreen : SearchableSettings {
         return listOf(
             Preference.PreferenceGroup(
                 title = stringResource(MR.strings.label_sources),
-                preferenceItems = listOf(
+                preferenceItems = persistentListOf(
                     Preference.PreferenceItem.SwitchPreference(
                         pref = sourcePreferences.hideInLibraryItems(),
                         title = stringResource(MR.strings.pref_hide_in_library_items),
@@ -36,7 +37,7 @@ object SettingsBrowseScreen : SearchableSettings {
             ),
             Preference.PreferenceGroup(
                 title = stringResource(MR.strings.pref_category_nsfw_content),
-                preferenceItems = listOf(
+                preferenceItems = persistentListOf(
                     Preference.PreferenceItem.SwitchPreference(
                         pref = sourcePreferences.showNsfwSource(),
                         title = stringResource(MR.strings.pref_show_nsfw_source),

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

@@ -3,12 +3,9 @@ package eu.kanade.presentation.more.settings.screen
 import android.content.ActivityNotFoundException
 import android.content.Intent
 import android.net.Uri
-import android.os.Environment
-import android.text.format.Formatter
 import androidx.activity.compose.ManagedActivityResultLauncher
 import androidx.activity.compose.rememberLauncherForActivityResult
 import androidx.activity.result.contract.ActivityResultContracts
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.MultiChoiceSegmentedButtonRow
@@ -31,13 +28,15 @@ 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.screen.data.RestoreBackupScreen
+import eu.kanade.presentation.more.settings.screen.data.StorageInfo
 import eu.kanade.presentation.more.settings.widget.BasePreferenceWidget
 import eu.kanade.presentation.more.settings.widget.PrefsHorizontalPadding
 import eu.kanade.presentation.util.relativeTimeSpanString
 import eu.kanade.tachiyomi.data.backup.create.BackupCreateJob
 import eu.kanade.tachiyomi.data.cache.ChapterCache
-import eu.kanade.tachiyomi.util.storage.DiskUtil
 import eu.kanade.tachiyomi.util.system.toast
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.persistentMapOf
 import logcat.LogPriority
 import tachiyomi.core.i18n.stringResource
 import tachiyomi.core.util.lang.launchNonCancellable
@@ -65,7 +64,7 @@ object SettingsDataScreen : SearchableSettings {
         val backupPreferences = Injekt.get<BackupPreferences>()
         val storagePreferences = Injekt.get<StoragePreferences>()
 
-        return listOf(
+        return persistentListOf(
             getStorageLocationPref(storagePreferences = storagePreferences),
             Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.pref_storage_location_info)),
 
@@ -142,7 +141,7 @@ object SettingsDataScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_backup),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 // Manual actions
                 Preference.PreferenceItem.CustomPreference(
                     title = stringResource(restorePreferenceKeyString),
@@ -177,7 +176,7 @@ object SettingsDataScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = backupPreferences.backupInterval(),
                     title = stringResource(MR.strings.pref_backup_interval),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         0 to stringResource(MR.strings.off),
                         6 to stringResource(MR.strings.update_6hour),
                         12 to stringResource(MR.strings.update_12hour),
@@ -200,8 +199,8 @@ object SettingsDataScreen : SearchableSettings {
 
     @Composable
     private fun getDataGroup(): Preference.PreferenceGroup {
-        val scope = rememberCoroutineScope()
         val context = LocalContext.current
+        val scope = rememberCoroutineScope()
         val libraryPreferences = remember { Injekt.get<LibraryPreferences>() }
 
         val chapterCache = remember { Injekt.get<ChapterCache>() }
@@ -210,8 +209,19 @@ object SettingsDataScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.label_data),
-            preferenceItems = listOf(
-                getStorageInfoPref(cacheReadableSize),
+            preferenceItems = persistentListOf(
+                Preference.PreferenceItem.CustomPreference(
+                    title = stringResource(MR.strings.pref_storage_usage),
+                ) {
+                    BasePreferenceWidget(
+                        title = stringResource(MR.strings.pref_storage_usage),
+                        subcomponent = {
+                            StorageInfo(
+                                modifier = Modifier.padding(horizontal = PrefsHorizontalPadding),
+                            )
+                        },
+                    )
+                },
 
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.pref_clear_chapter_cache),
@@ -238,31 +248,4 @@ object SettingsDataScreen : SearchableSettings {
             ),
         )
     }
-
-    @Composable
-    fun getStorageInfoPref(
-        chapterCacheReadableSize: String,
-    ): Preference.PreferenceItem.CustomPreference {
-        val context = LocalContext.current
-        val available = remember {
-            Formatter.formatFileSize(context, DiskUtil.getAvailableStorageSpace(Environment.getDataDirectory()))
-        }
-        val total = remember {
-            Formatter.formatFileSize(context, DiskUtil.getTotalStorageSpace(Environment.getDataDirectory()))
-        }
-
-        return Preference.PreferenceItem.CustomPreference(
-            title = stringResource(MR.strings.pref_storage_usage),
-        ) {
-            BasePreferenceWidget(
-                title = stringResource(MR.strings.pref_storage_usage),
-                subcomponent = {
-                    // TODO: downloads, SD cards, bar representation?, i18n
-                    Box(modifier = Modifier.padding(horizontal = PrefsHorizontalPadding)) {
-                        Text(text = "Available: $available / $total (chapter cache: $chapterCacheReadableSize)")
-                    }
-                },
-            )
-        }
-    }
 }

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

@@ -12,6 +12,9 @@ import androidx.compose.ui.util.fastMap
 import eu.kanade.presentation.category.visualName
 import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.widget.TriStateListDialog
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.collections.immutable.toImmutableMap
 import kotlinx.coroutines.runBlocking
 import tachiyomi.domain.category.interactor.GetCategories
 import tachiyomi.domain.category.model.Category
@@ -68,7 +71,7 @@ object SettingsDownloadScreen : SearchableSettings {
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_delete_chapters),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = downloadPreferences.removeAfterMarkedAsRead(),
                     title = stringResource(MR.strings.pref_remove_after_marked_as_read),
@@ -76,7 +79,7 @@ object SettingsDownloadScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = downloadPreferences.removeAfterReadSlots(),
                     title = stringResource(MR.strings.pref_remove_after_read),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         -1 to stringResource(MR.strings.disabled),
                         0 to stringResource(MR.strings.last_read_chapter),
                         1 to stringResource(MR.strings.second_to_last),
@@ -105,7 +108,9 @@ object SettingsDownloadScreen : SearchableSettings {
         return Preference.PreferenceItem.MultiSelectListPreference(
             pref = downloadPreferences.removeExcludeCategories(),
             title = stringResource(MR.strings.pref_remove_exclude_categories),
-            entries = categories().associate { it.id.toString() to it.visualName },
+            entries = categories()
+                .associate { it.id.toString() to it.visualName }
+                .toImmutableMap(),
         )
     }
 
@@ -142,7 +147,7 @@ object SettingsDownloadScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_auto_download),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = downloadNewChaptersPref,
                     title = stringResource(MR.strings.pref_download_new),
@@ -167,17 +172,19 @@ object SettingsDownloadScreen : SearchableSettings {
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.download_ahead),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = downloadPreferences.autoDownloadWhileReading(),
                     title = stringResource(MR.strings.auto_download_while_reading),
-                    entries = listOf(0, 2, 3, 5, 10).associateWith {
-                        if (it == 0) {
-                            stringResource(MR.strings.disabled)
-                        } else {
-                            pluralStringResource(MR.plurals.next_unread_chapters, count = it, it)
+                    entries = listOf(0, 2, 3, 5, 10)
+                        .associateWith {
+                            if (it == 0) {
+                                stringResource(MR.strings.disabled)
+                            } else {
+                                pluralStringResource(MR.plurals.next_unread_chapters, count = it, it)
+                            }
                         }
-                    },
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.download_ahead_info)),
             ),

+ 12 - 9
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsLibraryScreen.kt

@@ -20,6 +20,9 @@ import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.presentation.more.settings.widget.TriStateListDialog
 import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
 import eu.kanade.tachiyomi.ui.category.CategoryScreen
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.collections.immutable.toImmutableMap
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import tachiyomi.domain.category.interactor.GetCategories
@@ -79,7 +82,7 @@ object SettingsLibraryScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.categories),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = stringResource(MR.strings.action_edit_categories),
                     subtitle = pluralStringResource(
@@ -93,7 +96,7 @@ object SettingsLibraryScreen : SearchableSettings {
                     pref = libraryPreferences.defaultCategory(),
                     title = stringResource(MR.strings.default_category),
                     subtitle = selectedCategory?.visualName ?: stringResource(MR.strings.default_category_summary),
-                    entries = ids.zip(labels).toMap(),
+                    entries = ids.zip(labels).toMap().toImmutableMap(),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = libraryPreferences.categorizedDisplaySettings(),
@@ -146,11 +149,11 @@ object SettingsLibraryScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_library_update),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = autoUpdateIntervalPref,
                     title = stringResource(MR.strings.pref_library_update_interval),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         0 to stringResource(MR.strings.update_never),
                         12 to stringResource(MR.strings.update_12hour),
                         24 to stringResource(MR.strings.update_24hour),
@@ -168,7 +171,7 @@ object SettingsLibraryScreen : SearchableSettings {
                     enabled = autoUpdateInterval > 0,
                     title = stringResource(MR.strings.pref_library_update_restriction),
                     subtitle = stringResource(MR.strings.restrictions),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         DEVICE_ONLY_ON_WIFI to stringResource(MR.strings.connected_to_wifi),
                         DEVICE_NETWORK_NOT_METERED to stringResource(MR.strings.network_not_metered),
                         DEVICE_CHARGING to stringResource(MR.strings.charging),
@@ -196,7 +199,7 @@ object SettingsLibraryScreen : SearchableSettings {
                 Preference.PreferenceItem.MultiSelectListPreference(
                     pref = libraryPreferences.autoUpdateMangaRestrictions(),
                     title = stringResource(MR.strings.pref_library_update_manga_restriction),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         MANGA_HAS_UNREAD to stringResource(MR.strings.pref_update_only_completely_read),
                         MANGA_NON_READ to stringResource(MR.strings.pref_update_only_started),
                         MANGA_NON_COMPLETED to stringResource(MR.strings.pref_update_only_non_completed),
@@ -217,11 +220,11 @@ object SettingsLibraryScreen : SearchableSettings {
     ): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_chapter_swipe),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = libraryPreferences.swipeToStartAction(),
                     title = stringResource(MR.strings.pref_chapter_swipe_start),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         LibraryPreferences.ChapterSwipeAction.Disabled to
                             stringResource(MR.strings.disabled),
                         LibraryPreferences.ChapterSwipeAction.ToggleBookmark to
@@ -235,7 +238,7 @@ object SettingsLibraryScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = libraryPreferences.swipeToEndAction(),
                     title = stringResource(MR.strings.pref_chapter_swipe_end),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         LibraryPreferences.ChapterSwipeAction.Disabled to
                             stringResource(MR.strings.disabled),
                         LibraryPreferences.ChapterSwipeAction.ToggleBookmark to

+ 32 - 19
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt

@@ -10,6 +10,9 @@ import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderOrientation
 import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.persistentMapOf
+import kotlinx.collections.immutable.toImmutableMap
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.i18n.stringResource
 import tachiyomi.presentation.core.util.collectAsState
@@ -31,12 +34,13 @@ object SettingsReaderScreen : SearchableSettings {
                 pref = readerPref.defaultReadingMode(),
                 title = stringResource(MR.strings.pref_viewer_type),
                 entries = ReadingMode.entries.drop(1)
-                    .associate { it.flagValue to stringResource(it.stringRes) },
+                    .associate { it.flagValue to stringResource(it.stringRes) }
+                    .toImmutableMap(),
             ),
             Preference.PreferenceItem.ListPreference(
                 pref = readerPref.doubleTapAnimSpeed(),
                 title = stringResource(MR.strings.pref_double_tap_anim_speed),
-                entries = mapOf(
+                entries = persistentMapOf(
                     1 to stringResource(MR.strings.double_tap_anim_speed_0),
                     500 to stringResource(MR.strings.double_tap_anim_speed_normal),
                     250 to stringResource(MR.strings.double_tap_anim_speed_fast),
@@ -82,17 +86,18 @@ object SettingsReaderScreen : SearchableSettings {
         val fullscreen by fullscreenPref.collectAsState()
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_display),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.defaultOrientationType(),
                     title = stringResource(MR.strings.pref_rotation_type),
                     entries = ReaderOrientation.entries.drop(1)
-                        .associate { it.flagValue to stringResource(it.stringRes) },
+                        .associate { it.flagValue to stringResource(it.stringRes) }
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.readerTheme(),
                     title = stringResource(MR.strings.pref_reader_theme),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         1 to stringResource(MR.strings.black_background),
                         2 to stringResource(MR.strings.gray_background),
                         0 to stringResource(MR.strings.white_background),
@@ -126,7 +131,7 @@ object SettingsReaderScreen : SearchableSettings {
     private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_category_reading),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.skipRead(),
                     title = stringResource(MR.strings.pref_skip_read_chapters),
@@ -161,23 +166,26 @@ object SettingsReaderScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pager_viewer),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = navModePref,
                     title = stringResource(MR.strings.pref_viewer_nav),
                     entries = ReaderPreferences.TapZones
                         .mapIndexed { index, it -> index to stringResource(it) }
-                        .toMap(),
+                        .toMap()
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.pagerNavInverted(),
                     title = stringResource(MR.strings.pref_read_with_tapping_inverted),
-                    entries = listOf(
+                    entries = persistentListOf(
                         ReaderPreferences.TappingInvertMode.NONE,
                         ReaderPreferences.TappingInvertMode.HORIZONTAL,
                         ReaderPreferences.TappingInvertMode.VERTICAL,
                         ReaderPreferences.TappingInvertMode.BOTH,
-                    ).associateWith { stringResource(it.titleRes) },
+                    )
+                        .associateWith { stringResource(it.titleRes) }
+                        .toImmutableMap(),
                     enabled = navMode != 5,
                 ),
                 Preference.PreferenceItem.ListPreference(
@@ -185,14 +193,16 @@ object SettingsReaderScreen : SearchableSettings {
                     title = stringResource(MR.strings.pref_image_scale_type),
                     entries = ReaderPreferences.ImageScaleType
                         .mapIndexed { index, it -> index + 1 to stringResource(it) }
-                        .toMap(),
+                        .toMap()
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.zoomStart(),
                     title = stringResource(MR.strings.pref_zoom_start),
                     entries = ReaderPreferences.ZoomStart
                         .mapIndexed { index, it -> index + 1 to stringResource(it) }
-                        .toMap(),
+                        .toMap()
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.cropBorders(),
@@ -255,23 +265,26 @@ object SettingsReaderScreen : SearchableSettings {
 
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.webtoon_viewer),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.ListPreference(
                     pref = navModePref,
                     title = stringResource(MR.strings.pref_viewer_nav),
                     entries = ReaderPreferences.TapZones
                         .mapIndexed { index, it -> index to stringResource(it) }
-                        .toMap(),
+                        .toMap()
+                        .toImmutableMap(),
                 ),
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.webtoonNavInverted(),
                     title = stringResource(MR.strings.pref_read_with_tapping_inverted),
-                    entries = listOf(
+                    entries = persistentListOf(
                         ReaderPreferences.TappingInvertMode.NONE,
                         ReaderPreferences.TappingInvertMode.HORIZONTAL,
                         ReaderPreferences.TappingInvertMode.VERTICAL,
                         ReaderPreferences.TappingInvertMode.BOTH,
-                    ).associateWith { stringResource(it.titleRes) },
+                    )
+                        .associateWith { stringResource(it.titleRes) }
+                        .toImmutableMap(),
                     enabled = navMode != 5,
                 ),
                 Preference.PreferenceItem.SliderPreference(
@@ -288,7 +301,7 @@ object SettingsReaderScreen : SearchableSettings {
                 Preference.PreferenceItem.ListPreference(
                     pref = readerPreferences.readerHideThreshold(),
                     title = stringResource(MR.strings.pref_hide_threshold),
-                    entries = mapOf(
+                    entries = persistentMapOf(
                         ReaderPreferences.ReaderHideThreshold.HIGHEST to stringResource(MR.strings.pref_highest),
                         ReaderPreferences.ReaderHideThreshold.HIGH to stringResource(MR.strings.pref_high),
                         ReaderPreferences.ReaderHideThreshold.LOW to stringResource(MR.strings.pref_low),
@@ -341,7 +354,7 @@ object SettingsReaderScreen : SearchableSettings {
         val readWithVolumeKeys by readWithVolumeKeysPref.collectAsState()
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_reader_navigation),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readWithVolumeKeysPref,
                     title = stringResource(MR.strings.pref_read_with_volume_keys),
@@ -359,7 +372,7 @@ object SettingsReaderScreen : SearchableSettings {
     private fun getActionsGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = stringResource(MR.strings.pref_reader_actions),
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.SwitchPreference(
                     pref = readerPreferences.readWithLongTap(),
                     title = stringResource(MR.strings.pref_read_with_long_tap),

+ 7 - 3
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsSecurityScreen.kt

@@ -10,6 +10,8 @@ import eu.kanade.presentation.more.settings.Preference
 import eu.kanade.tachiyomi.core.security.SecurityPreferences
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.authenticate
 import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableMap
 import tachiyomi.core.i18n.stringResource
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.i18n.pluralStringResource
@@ -55,7 +57,8 @@ object SettingsSecurityScreen : SearchableSettings {
                             0 -> stringResource(MR.strings.lock_always)
                             else -> pluralStringResource(MR.plurals.lock_after_mins, count = it, it)
                         }
-                    },
+                    }
+                    .toImmutableMap(),
                 onValueChanged = {
                     (context as FragmentActivity).authenticate(
                         title = context.stringResource(MR.strings.lock_when_idle),
@@ -70,14 +73,15 @@ object SettingsSecurityScreen : SearchableSettings {
                 pref = securityPreferences.secureScreen(),
                 title = stringResource(MR.strings.secure_screen),
                 entries = SecurityPreferences.SecureScreenMode.entries
-                    .associateWith { stringResource(it.titleRes) },
+                    .associateWith { stringResource(it.titleRes) }
+                    .toImmutableMap(),
             ),
             Preference.PreferenceItem.InfoPreference(stringResource(MR.strings.secure_screen_summary)),
         )
     }
 }
 
-private val LockAfterValues = listOf(
+private val LockAfterValues = persistentListOf(
     0, // Always
     1,
     2,

+ 14 - 10
app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsTrackingScreen.kt

@@ -51,6 +51,8 @@ import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
 import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
 import eu.kanade.tachiyomi.util.system.openInBrowser
 import eu.kanade.tachiyomi.util.system.toast
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
 import tachiyomi.core.util.lang.launchIO
 import tachiyomi.core.util.lang.withUIContext
 import tachiyomi.domain.source.service.SourceManager
@@ -125,7 +127,7 @@ object SettingsTrackingScreen : SearchableSettings {
             ),
             Preference.PreferenceGroup(
                 title = stringResource(MR.strings.services),
-                preferenceItems = listOf(
+                preferenceItems = persistentListOf(
                     Preference.PreferenceItem.TrackerPreference(
                         title = trackerManager.myAnimeList.name,
                         tracker = trackerManager.myAnimeList,
@@ -167,15 +169,17 @@ object SettingsTrackingScreen : SearchableSettings {
             ),
             Preference.PreferenceGroup(
                 title = stringResource(MR.strings.enhanced_services),
-                preferenceItems = enhancedTrackers.first
-                    .map { service ->
-                        Preference.PreferenceItem.TrackerPreference(
-                            title = service.name,
-                            tracker = service,
-                            login = { (service as EnhancedTracker).loginNoop() },
-                            logout = service::logout,
-                        )
-                    } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo)),
+                preferenceItems = (
+                    enhancedTrackers.first
+                        .map { service ->
+                            Preference.PreferenceItem.TrackerPreference(
+                                title = service.name,
+                                tracker = service,
+                                login = { (service as EnhancedTracker).loginNoop() },
+                                logout = service::logout,
+                            )
+                        } + listOf(Preference.PreferenceItem.InfoPreference(enhancedTrackerInfo))
+                    ).toImmutableList(),
             ),
         )
     }

+ 2 - 1
app/src/main/java/eu/kanade/presentation/more/settings/screen/data/CreateBackupScreen.kt

@@ -36,6 +36,7 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.toast
 import kotlinx.collections.immutable.PersistentSet
 import kotlinx.collections.immutable.minus
+import kotlinx.collections.immutable.persistentMapOf
 import kotlinx.collections.immutable.persistentSetOf
 import kotlinx.collections.immutable.plus
 import kotlinx.coroutines.flow.update
@@ -170,7 +171,7 @@ private class CreateBackupScreenModel : StateScreenModel<CreateBackupScreenModel
     )
 }
 
-private val BackupChoices = mapOf(
+private val BackupChoices = persistentMapOf(
     BackupCreateFlags.BACKUP_CATEGORY to MR.strings.categories,
     BackupCreateFlags.BACKUP_CHAPTER to MR.strings.chapters,
     BackupCreateFlags.BACKUP_TRACK to MR.strings.track,

+ 117 - 0
app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt

@@ -0,0 +1,117 @@
+package eu.kanade.presentation.more.settings.screen.data
+
+import android.text.format.Formatter
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.CornerRadius
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.RoundRect
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import eu.kanade.tachiyomi.util.storage.DiskUtil
+import tachiyomi.i18n.MR
+import tachiyomi.presentation.core.i18n.stringResource
+import java.io.File
+
+@Composable
+fun StorageInfo(
+    modifier: Modifier = Modifier,
+) {
+    val context = LocalContext.current
+    val storages = remember { DiskUtil.getExternalStorages(context) }
+
+    Column(
+        modifier = modifier,
+        verticalArrangement = Arrangement.spacedBy(8.dp),
+    ) {
+        storages.forEach {
+            StorageInfo(it)
+        }
+    }
+}
+
+@Composable
+private fun StorageInfo(
+    file: File,
+) {
+    val context = LocalContext.current
+    val layoutDirection = LocalLayoutDirection.current
+
+    val available = remember(file) { DiskUtil.getAvailableStorageSpace(file) }
+    val availableText = remember(available) { Formatter.formatFileSize(context, available) }
+    val total = remember(file) { DiskUtil.getTotalStorageSpace(file) }
+    val totalText = remember(total) { Formatter.formatFileSize(context, total) }
+
+    val cornerRadius = CornerRadius(100f, 100f)
+    val usedBarColor = MaterialTheme.colorScheme.primary
+    val totalBarColor = MaterialTheme.colorScheme.surfaceVariant
+
+    Column(
+        verticalArrangement = Arrangement.spacedBy(4.dp),
+    ) {
+        Text(
+            text = file.absolutePath,
+            fontWeight = FontWeight.Medium,
+        )
+
+        Canvas(
+            modifier = Modifier
+                .fillMaxWidth()
+                .height(12.dp),
+        ) {
+            drawRoundRect(
+                color = totalBarColor,
+                cornerRadius = cornerRadius,
+            )
+
+            drawPath(
+                path = Path().apply {
+                    val pathSize = Size(
+                        width = (1 - (available / total.toFloat())) * size.width,
+                        height = size.height,
+                    )
+                    addRoundRect(
+                        if (layoutDirection == LayoutDirection.Ltr) {
+                            RoundRect(
+                                rect = Rect(
+                                    offset = Offset(0f, 0f),
+                                    size = pathSize,
+                                ),
+                                topLeft = cornerRadius,
+                                bottomLeft = cornerRadius,
+                            )
+                        } else {
+                            RoundRect(
+                                rect = Rect(
+                                    offset = Offset(size.width - pathSize.width, 0f),
+                                    size = pathSize,
+                                ),
+                                topRight = cornerRadius,
+                                bottomRight = cornerRadius,
+                            )
+                        },
+                    )
+                },
+                color = usedBarColor,
+            )
+        }
+
+        Text(
+            text = stringResource(MR.strings.available_disk_space_info, availableText, totalText),
+        )
+    }
+}

+ 8 - 6
app/src/main/java/eu/kanade/presentation/more/settings/screen/debug/DebugInfoScreen.kt

@@ -15,6 +15,8 @@ import eu.kanade.presentation.more.settings.screen.about.AboutScreen
 import eu.kanade.presentation.util.Screen
 import eu.kanade.tachiyomi.util.system.DeviceUtil
 import eu.kanade.tachiyomi.util.system.WebViewUtil
+import kotlinx.collections.immutable.mutate
+import kotlinx.collections.immutable.persistentListOf
 import kotlinx.coroutines.guava.await
 import tachiyomi.i18n.MR
 
@@ -47,7 +49,7 @@ class DebugInfoScreen : Screen() {
     private fun getAppInfoGroup(): Preference.PreferenceGroup {
         return Preference.PreferenceGroup(
             title = "App info",
-            preferenceItems = listOf(
+            preferenceItems = persistentListOf(
                 Preference.PreferenceItem.TextPreference(
                     title = "Version",
                     subtitle = AboutScreen.getVersionName(false),
@@ -96,8 +98,8 @@ class DebugInfoScreen : Screen() {
     }
 
     private fun getDeviceInfoGroup(): Preference.PreferenceGroup {
-        val items = buildList {
-            add(
+        val items = persistentListOf<Preference.PreferenceItem<out Any>>().mutate {
+            it.add(
                 Preference.PreferenceItem.TextPreference(
                     title = "Model",
                     subtitle = "${Build.MANUFACTURER} ${Build.MODEL} (${Build.DEVICE})",
@@ -105,14 +107,14 @@ class DebugInfoScreen : Screen() {
             )
 
             if (DeviceUtil.oneUiVersion != null) {
-                add(
+                it.add(
                     Preference.PreferenceItem.TextPreference(
                         title = "OneUI version",
                         subtitle = "${DeviceUtil.oneUiVersion}",
                     ),
                 )
             } else if (DeviceUtil.miuiMajorVersion != null) {
-                add(
+                it.add(
                     Preference.PreferenceItem.TextPreference(
                         title = "MIUI version",
                         subtitle = "${DeviceUtil.miuiMajorVersion}",
@@ -127,7 +129,7 @@ class DebugInfoScreen : Screen() {
             } else {
                 Build.VERSION.RELEASE
             }
-            add(
+            it.add(
                 Preference.PreferenceItem.TextPreference(
                     title = "Android version",
                     subtitle = "$androidVersion (${Build.DISPLAY})",

+ 2 - 1
app/src/main/java/eu/kanade/presentation/reader/ChapterTransition.kt

@@ -37,6 +37,7 @@ import eu.kanade.presentation.theme.TachiyomiTheme
 import eu.kanade.tachiyomi.data.database.models.toDomainChapter
 import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
 import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
+import kotlinx.collections.immutable.persistentMapOf
 import tachiyomi.domain.chapter.model.Chapter
 import tachiyomi.domain.chapter.service.calculateChapterGap
 import tachiyomi.i18n.MR
@@ -234,7 +235,7 @@ private fun ChapterText(
             maxLines = 5,
             overflow = TextOverflow.Ellipsis,
             style = MaterialTheme.typography.titleLarge,
-            inlineContent = mapOf(
+            inlineContent = persistentMapOf(
                 DownloadedIconContentId to InlineTextContent(
                     Placeholder(
                         width = 22.sp,

+ 2 - 1
app/src/main/java/eu/kanade/presentation/track/TrackInfoDialogSelector.kt

@@ -34,6 +34,7 @@ import androidx.compose.ui.unit.dp
 import dev.icerock.moko.resources.StringResource
 import eu.kanade.presentation.theme.TachiyomiTheme
 import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentMapOf
 import kotlinx.collections.immutable.toImmutableList
 import tachiyomi.i18n.MR
 import tachiyomi.presentation.core.components.ScrollbarLazyColumn
@@ -233,7 +234,7 @@ private fun TrackStatusSelectorPreviews() {
             TrackStatusSelector(
                 selection = 1,
                 onSelectionChange = {},
-                selections = mapOf(
+                selections = persistentMapOf(
                     // Anilist values
                     1 to MR.strings.reading,
                     2 to MR.strings.plan_to_read,

+ 4 - 4
app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt

@@ -14,7 +14,6 @@ import okio.buffer
 import okio.sink
 import tachiyomi.core.util.system.logcat
 import tachiyomi.domain.chapter.model.Chapter
-import uy.kohesive.injekt.injectLazy
 import java.io.File
 import java.io.IOException
 
@@ -26,9 +25,10 @@ import java.io.IOException
  *
  * @param context the application context.
  */
-class ChapterCache(private val context: Context) {
-
-    private val json: Json by injectLazy()
+class ChapterCache(
+    private val context: Context,
+    private val json: Json,
+) {
 
     /** Cache class used for cache management. */
     private val diskCache = DiskLruCache.open(

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt

@@ -111,7 +111,7 @@ class AppModule(val app: Application) : InjektModule {
             ProtoBuf
         }
 
-        addSingletonFactory { ChapterCache(app) }
+        addSingletonFactory { ChapterCache(app, get()) }
         addSingletonFactory { CoverCache(app) }
 
         addSingletonFactory { NetworkHelper(app, get()) }

+ 19 - 0
core/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt

@@ -3,13 +3,32 @@ package eu.kanade.tachiyomi.util.storage
 import android.content.Context
 import android.media.MediaScannerConnection
 import android.net.Uri
+import android.os.Environment
 import android.os.StatFs
+import androidx.core.content.ContextCompat
 import com.hippo.unifile.UniFile
 import eu.kanade.tachiyomi.util.lang.Hash
 import java.io.File
 
 object DiskUtil {
 
+    /**
+     * Returns the root folders of all the available external storages.
+     */
+    fun getExternalStorages(context: Context): List<File> {
+        return ContextCompat.getExternalFilesDirs(context, null)
+            .filterNotNull()
+            .mapNotNull {
+                val file = File(it.absolutePath.substringBefore("/Android/"))
+                val state = Environment.getExternalStorageState(file)
+                if (state == Environment.MEDIA_MOUNTED || state == Environment.MEDIA_MOUNTED_READ_ONLY) {
+                    file
+                } else {
+                    null
+                }
+            }
+    }
+
     fun hashKeyForDisk(key: String): String {
         return Hash.md5(key)
     }

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

@@ -519,6 +519,7 @@
     <string name="last_auto_backup_info">Last automatically backed up: %s</string>
     <string name="label_data">Data</string>
     <string name="pref_storage_usage">Storage usage</string>
+    <string name="available_disk_space_info">Available: %1$s / Total: %2$s</string>
     <string name="pref_clear_chapter_cache">Clear chapter cache</string>
     <string name="used_cache">Used: %1$s</string>
     <string name="cache_deleted">Cache cleared, %1$d files deleted</string>

+ 2 - 1
presentation-core/src/main/java/tachiyomi/presentation/core/components/Badges.kt

@@ -21,6 +21,7 @@ import androidx.compose.ui.text.PlaceholderVerticalAlign
 import androidx.compose.ui.text.buildAnnotatedString
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
+import kotlinx.collections.immutable.persistentMapOf
 
 @Composable
 fun BadgeGroup(
@@ -66,7 +67,7 @@ fun Badge(
     val text = buildAnnotatedString {
         appendInlineContent(iconContentPlaceholder)
     }
-    val inlineContent = mapOf(
+    val inlineContent = persistentMapOf(
         Pair(
             iconContentPlaceholder,
             InlineTextContent(