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
This commit is contained in:
arkon 2023-12-25 18:11:22 -05:00
parent 950b4a6c90
commit f31bc47757
20 changed files with 301 additions and 128 deletions

View File

@ -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()
}

View File

@ -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

View File

@ -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()
}
}

View File

@ -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),

View File

@ -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)")
}
},
)
}
}
}

View File

@ -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)),
),

View File

@ -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

View File

@ -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),

View File

@ -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,

View File

@ -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(),
),
)
}

View File

@ -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,

View File

@ -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),
)
}
}

View File

@ -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})",

View File

@ -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,

View File

@ -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,

View File

@ -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(

View File

@ -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()) }

View File

@ -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)
}

View File

@ -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>

View File

@ -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(