Remove dead code
Mostly from settings rewrite, but some other things too.
This commit is contained in:
parent
5c5468f9af
commit
69cdba71eb
56 changed files with 6 additions and 4397 deletions
|
@ -254,7 +254,6 @@ dependencies {
|
||||||
|
|
||||||
// UI libraries
|
// UI libraries
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
implementation(libs.androidprocessbutton)
|
|
||||||
implementation(libs.flexible.adapter.core)
|
implementation(libs.flexible.adapter.core)
|
||||||
implementation(libs.flexible.adapter.ui)
|
implementation(libs.flexible.adapter.ui)
|
||||||
implementation(libs.photoview)
|
implementation(libs.photoview)
|
||||||
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.kanade.presentation.more.settings
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Search
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import eu.kanade.presentation.components.AppBar
|
|
||||||
import eu.kanade.presentation.components.AppBarActions
|
|
||||||
import eu.kanade.presentation.components.PreferenceRow
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsMainScreen(
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
sections: List<SettingsSection>,
|
|
||||||
onClickSearch: () -> Unit,
|
|
||||||
) {
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
AppBar(
|
|
||||||
title = stringResource(R.string.label_settings),
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
actions = {
|
|
||||||
AppBarActions(
|
|
||||||
listOf(
|
|
||||||
AppBar.Action(
|
|
||||||
title = stringResource(R.string.action_search),
|
|
||||||
icon = Icons.Outlined.Search,
|
|
||||||
onClick = onClickSearch,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
ScrollbarLazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
sections.map {
|
|
||||||
item {
|
|
||||||
PreferenceRow(
|
|
||||||
title = stringResource(it.titleRes),
|
|
||||||
painter = it.painter,
|
|
||||||
onClick = it.onClick,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class SettingsSection(
|
|
||||||
@StringRes val titleRes: Int,
|
|
||||||
val painter: Painter,
|
|
||||||
val onClick: () -> Unit,
|
|
||||||
)
|
|
|
@ -1,116 +0,0 @@
|
||||||
package eu.kanade.presentation.more.settings
|
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.components.ScrollbarLazyColumn
|
|
||||||
import eu.kanade.presentation.components.SearchToolbar
|
|
||||||
import eu.kanade.presentation.util.horizontalPadding
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchHelper
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.search.SettingsSearchPresenter
|
|
||||||
import kotlin.reflect.full.createInstance
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SettingsSearchScreen(
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
presenter: SettingsSearchPresenter,
|
|
||||||
onClickResult: (SettingsController) -> Unit,
|
|
||||||
) {
|
|
||||||
val results by presenter.state.collectAsState()
|
|
||||||
var query by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
val keyboardController = LocalSoftwareKeyboardController.current
|
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
SearchToolbar(
|
|
||||||
searchQuery = query,
|
|
||||||
onChangeSearchQuery = {
|
|
||||||
query = it
|
|
||||||
presenter.searchSettings(it)
|
|
||||||
},
|
|
||||||
placeholderText = stringResource(R.string.action_search_settings),
|
|
||||||
onClickCloseSearch = navigateUp,
|
|
||||||
onClickResetSearch = { query = "" },
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
keyboardOptions = KeyboardOptions.Default.copy(
|
|
||||||
imeAction = ImeAction.Search,
|
|
||||||
),
|
|
||||||
keyboardActions = KeyboardActions(
|
|
||||||
onSearch = {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
keyboardController?.hide()
|
|
||||||
},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { contentPadding ->
|
|
||||||
ScrollbarLazyColumn(
|
|
||||||
contentPadding = contentPadding,
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = results,
|
|
||||||
key = { it.key.toString() },
|
|
||||||
) { result ->
|
|
||||||
SearchResult(result, onClickResult)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SearchResult(
|
|
||||||
result: SettingsSearchHelper.SettingsSearchResult,
|
|
||||||
onClickResult: (SettingsController) -> Unit,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable {
|
|
||||||
// Must pass a new Controller instance to avoid this error
|
|
||||||
// https://github.com/bluelinelabs/Conductor/issues/446
|
|
||||||
val controller = result.searchController::class.createInstance()
|
|
||||||
controller.preferenceKey = result.key
|
|
||||||
onClickResult(controller)
|
|
||||||
}
|
|
||||||
.padding(horizontal = horizontalPadding, vertical = 8.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = result.title,
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = result.summary,
|
|
||||||
style = MaterialTheme.typography.bodySmall.copy(
|
|
||||||
color = MaterialTheme.colorScheme.outline,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = result.breadcrumb,
|
|
||||||
style = MaterialTheme.typography.bodySmall,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package eu.kanade.presentation.more.settings.database
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import eu.kanade.presentation.components.Scaffold
|
|
||||||
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseContent
|
|
||||||
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseDeleteDialog
|
|
||||||
import eu.kanade.presentation.more.settings.database.components.ClearDatabaseToolbar
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ClearDatabaseScreen(
|
|
||||||
presenter: ClearDatabasePresenter,
|
|
||||||
navigateUp: () -> Unit,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
Scaffold(
|
|
||||||
topBar = { scrollBehavior ->
|
|
||||||
ClearDatabaseToolbar(
|
|
||||||
state = presenter,
|
|
||||||
navigateUp = navigateUp,
|
|
||||||
onClickSelectAll = { presenter.selectAll() },
|
|
||||||
onClickInvertSelection = { presenter.invertSelection() },
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) { paddingValues ->
|
|
||||||
ClearDatabaseContent(
|
|
||||||
state = presenter,
|
|
||||||
contentPadding = paddingValues,
|
|
||||||
onClickSelection = { source ->
|
|
||||||
presenter.toggleSelection(source)
|
|
||||||
},
|
|
||||||
onClickDelete = {
|
|
||||||
presenter.dialog = ClearDatabasePresenter.Dialog.Delete(presenter.selection)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val dialog = presenter.dialog
|
|
||||||
if (dialog is ClearDatabasePresenter.Dialog.Delete) {
|
|
||||||
ClearDatabaseDeleteDialog(
|
|
||||||
onDismissRequest = { presenter.dialog = null },
|
|
||||||
onDelete = {
|
|
||||||
presenter.removeMangaBySourceId(dialog.sourceIds)
|
|
||||||
presenter.clearSelection()
|
|
||||||
presenter.dialog = null
|
|
||||||
context.toast(R.string.clear_database_completed)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,14 +6,13 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import eu.kanade.domain.source.model.SourceWithCount
|
import eu.kanade.domain.source.model.SourceWithCount
|
||||||
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabasePresenter
|
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
interface ClearDatabaseState {
|
interface ClearDatabaseState {
|
||||||
val items: List<SourceWithCount>
|
val items: List<SourceWithCount>
|
||||||
val selection: List<Long>
|
val selection: List<Long>
|
||||||
val isEmpty: Boolean
|
val isEmpty: Boolean
|
||||||
var dialog: ClearDatabasePresenter.Dialog?
|
var dialog: Dialog?
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ClearDatabaseState(): ClearDatabaseState {
|
fun ClearDatabaseState(): ClearDatabaseState {
|
||||||
|
@ -24,5 +23,9 @@ class ClearDatabaseStateImpl : ClearDatabaseState {
|
||||||
override var items: List<SourceWithCount> by mutableStateOf(emptyList())
|
override var items: List<SourceWithCount> by mutableStateOf(emptyList())
|
||||||
override var selection: List<Long> by mutableStateOf(emptyList())
|
override var selection: List<Long> by mutableStateOf(emptyList())
|
||||||
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
override val isEmpty: Boolean by derivedStateOf { items.isEmpty() }
|
||||||
override var dialog: ClearDatabasePresenter.Dialog? by mutableStateOf(null)
|
override var dialog: Dialog? by mutableStateOf(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class Dialog {
|
||||||
|
data class Delete(val sourceIds: List<Long>) : Dialog()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,401 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
|
||||||
import android.provider.Settings
|
|
||||||
import android.webkit.WebStorage
|
|
||||||
import android.webkit.WebView
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.domain.manga.repository.MangaRepository
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.domain.ui.model.TabletUiMode
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Target
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_ADGUARD
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_ALIDNS
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CLOUDFLARE
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_CONTROLD
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_DNSPOD
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_GOOGLE
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_MULLVAD
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_NJALLA
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD101
|
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_QUAD9
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.openInBrowser
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabaseController
|
|
||||||
import eu.kanade.tachiyomi.util.CrashLogUtil
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNonCancellable
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.defaultValue
|
|
||||||
import eu.kanade.tachiyomi.util.preference.editTextPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.listPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.isDevFlavor
|
|
||||||
import eu.kanade.tachiyomi.util.system.isPackageInstalled
|
|
||||||
import eu.kanade.tachiyomi.util.system.logcat
|
|
||||||
import eu.kanade.tachiyomi.util.system.powerManager
|
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import logcat.LogPriority
|
|
||||||
import rikka.sui.Sui
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class SettingsAdvancedController(
|
|
||||||
private val mangaRepository: MangaRepository = Injekt.get(),
|
|
||||||
) : SettingsController() {
|
|
||||||
|
|
||||||
private val network: NetworkHelper by injectLazy()
|
|
||||||
private val chapterCache: ChapterCache by injectLazy()
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
|
||||||
private val networkPreferences: NetworkPreferences by injectLazy()
|
|
||||||
private val libraryPreferences: LibraryPreferences by injectLazy()
|
|
||||||
private val uiPreferences: UiPreferences by injectLazy()
|
|
||||||
|
|
||||||
@SuppressLint("BatteryLife")
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_advanced
|
|
||||||
|
|
||||||
if (isDevFlavor.not()) {
|
|
||||||
switchPreference {
|
|
||||||
key = "acra.enable"
|
|
||||||
titleRes = R.string.pref_enable_acra
|
|
||||||
summaryRes = R.string.pref_acra_summary
|
|
||||||
defaultValue = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "dump_crash_logs"
|
|
||||||
titleRes = R.string.pref_dump_crash_logs
|
|
||||||
summaryRes = R.string.pref_dump_crash_logs_summary
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
viewScope.launchNonCancellable {
|
|
||||||
CrashLogUtil(context).dumpLogs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
key = networkPreferences.verboseLogging().key()
|
|
||||||
titleRes = R.string.pref_verbose_logging
|
|
||||||
summaryRes = R.string.pref_verbose_logging_summary
|
|
||||||
defaultValue = isDevFlavor
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
activity?.toast(R.string.requires_app_restart)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_background_activity
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_disable_battery_optimization"
|
|
||||||
titleRes = R.string.pref_disable_battery_optimization
|
|
||||||
summaryRes = R.string.pref_disable_battery_optimization_summary
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
val packageName: String = context.packageName
|
|
||||||
if (!context.powerManager.isIgnoringBatteryOptimizations(packageName)) {
|
|
||||||
try {
|
|
||||||
val intent = Intent().apply {
|
|
||||||
action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
|
|
||||||
data = "package:$packageName".toUri()
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
context.toast(R.string.battery_optimization_setting_activity_not_found)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.battery_optimization_disabled)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_dont_kill_my_app"
|
|
||||||
title = "Don't kill my app!"
|
|
||||||
summaryRes = R.string.about_dont_kill_my_app
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
openInBrowser("https://dontkillmyapp.com/")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_data
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = CLEAR_CACHE_KEY
|
|
||||||
titleRes = R.string.pref_clear_chapter_cache
|
|
||||||
summary = context.getString(R.string.used_cache, chapterCache.readableSize)
|
|
||||||
|
|
||||||
onClick { clearChapterCache() }
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(libraryPreferences.autoClearChapterCache())
|
|
||||||
titleRes = R.string.pref_auto_clear_chapter_cache
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_clear_database"
|
|
||||||
titleRes = R.string.pref_clear_database
|
|
||||||
summaryRes = R.string.pref_clear_database_summary
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
router.pushController(ClearDatabaseController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_network
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_clear_cookies"
|
|
||||||
titleRes = R.string.pref_clear_cookies
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
network.cookieManager.removeAll()
|
|
||||||
activity?.toast(R.string.cookies_cleared)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_clear_webview_data"
|
|
||||||
titleRes = R.string.pref_clear_webview_data
|
|
||||||
|
|
||||||
onClick { clearWebViewData() }
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
key = networkPreferences.dohProvider().key()
|
|
||||||
titleRes = R.string.pref_dns_over_https
|
|
||||||
entries = arrayOf(
|
|
||||||
context.getString(R.string.disabled),
|
|
||||||
"Cloudflare",
|
|
||||||
"Google",
|
|
||||||
"AdGuard",
|
|
||||||
"Quad9",
|
|
||||||
"AliDNS",
|
|
||||||
"DNSPod",
|
|
||||||
"360",
|
|
||||||
"Quad 101",
|
|
||||||
"Mullvad",
|
|
||||||
"Control D",
|
|
||||||
"Njalla",
|
|
||||||
)
|
|
||||||
entryValues = arrayOf(
|
|
||||||
"-1",
|
|
||||||
PREF_DOH_CLOUDFLARE.toString(),
|
|
||||||
PREF_DOH_GOOGLE.toString(),
|
|
||||||
PREF_DOH_ADGUARD.toString(),
|
|
||||||
PREF_DOH_QUAD9.toString(),
|
|
||||||
PREF_DOH_ALIDNS.toString(),
|
|
||||||
PREF_DOH_DNSPOD.toString(),
|
|
||||||
PREF_DOH_360.toString(),
|
|
||||||
PREF_DOH_QUAD101.toString(),
|
|
||||||
PREF_DOH_MULLVAD.toString(),
|
|
||||||
PREF_DOH_CONTROLD.toString(),
|
|
||||||
PREF_DOH_NJALLA.toString(),
|
|
||||||
)
|
|
||||||
defaultValue = "-1"
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
activity?.toast(R.string.requires_app_restart)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val defaultUserAgent = networkPreferences.defaultUserAgent()
|
|
||||||
editTextPreference {
|
|
||||||
key = defaultUserAgent.key()
|
|
||||||
titleRes = R.string.pref_user_agent_string
|
|
||||||
text = defaultUserAgent.get()
|
|
||||||
summary = network.defaultUserAgent
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
if (it.toString().isBlank()) {
|
|
||||||
activity?.toast(R.string.error_user_agent_string_blank)
|
|
||||||
} else {
|
|
||||||
text = it.toString().trim()
|
|
||||||
activity?.toast(R.string.requires_app_restart)
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_reset_user_agent"
|
|
||||||
titleRes = R.string.pref_reset_user_agent_string
|
|
||||||
|
|
||||||
visibleIf(defaultUserAgent) { it != defaultUserAgent.defaultValue() }
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
defaultUserAgent.delete()
|
|
||||||
activity?.toast(R.string.requires_app_restart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_library
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_refresh_library_covers"
|
|
||||||
titleRes = R.string.pref_refresh_library_covers
|
|
||||||
|
|
||||||
onClick { LibraryUpdateService.start(context, target = Target.COVERS) }
|
|
||||||
}
|
|
||||||
if (trackManager.hasLoggedServices()) {
|
|
||||||
preference {
|
|
||||||
key = "pref_refresh_library_tracking"
|
|
||||||
titleRes = R.string.pref_refresh_library_tracking
|
|
||||||
summaryRes = R.string.pref_refresh_library_tracking_summary
|
|
||||||
|
|
||||||
onClick { LibraryUpdateService.start(context, target = Target.TRACKING) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_reset_viewer_flags"
|
|
||||||
titleRes = R.string.pref_reset_viewer_flags
|
|
||||||
summaryRes = R.string.pref_reset_viewer_flags_summary
|
|
||||||
|
|
||||||
onClick { resetViewerFlags() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_extensions
|
|
||||||
|
|
||||||
listPreference {
|
|
||||||
bindTo(preferences.extensionInstaller())
|
|
||||||
titleRes = R.string.ext_installer_pref
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
// PackageInstaller doesn't work on MIUI properly for non-allowlisted apps
|
|
||||||
val values = if (DeviceUtil.isMiui) {
|
|
||||||
PreferenceValues.ExtensionInstaller.values()
|
|
||||||
.filter { it != PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER }
|
|
||||||
} else {
|
|
||||||
PreferenceValues.ExtensionInstaller.values().toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
entriesRes = values.map { it.titleResId }.toTypedArray()
|
|
||||||
entryValues = values.map { it.name }.toTypedArray()
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
if (it == PreferenceValues.ExtensionInstaller.SHIZUKU.name &&
|
|
||||||
!(context.isPackageInstalled("moe.shizuku.privileged.api") || Sui.isSui())
|
|
||||||
) {
|
|
||||||
MaterialAlertDialogBuilder(context)
|
|
||||||
.setTitle(R.string.ext_installer_shizuku)
|
|
||||||
.setMessage(R.string.ext_installer_shizuku_unavailable_dialog)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
openInBrowser("https://shizuku.rikka.app/download")
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.show()
|
|
||||||
false
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_display
|
|
||||||
|
|
||||||
listPreference {
|
|
||||||
bindTo(uiPreferences.tabletUiMode())
|
|
||||||
titleRes = R.string.pref_tablet_ui_mode
|
|
||||||
summary = "%s"
|
|
||||||
entriesRes = TabletUiMode.values().map { it.titleResId }.toTypedArray()
|
|
||||||
entryValues = TabletUiMode.values().map { it.name }.toTypedArray()
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
activity?.toast(R.string.requires_app_restart)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearChapterCache() {
|
|
||||||
val activity = activity ?: return
|
|
||||||
viewScope.launchNonCancellable {
|
|
||||||
try {
|
|
||||||
val deletedFiles = chapterCache.clear()
|
|
||||||
withUIContext {
|
|
||||||
activity.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
|
|
||||||
findPreference(CLEAR_CACHE_KEY)?.summary =
|
|
||||||
resources?.getString(R.string.used_cache, chapterCache.readableSize)
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
withUIContext { activity.toast(R.string.cache_delete_error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun clearWebViewData() {
|
|
||||||
val activity = activity ?: return
|
|
||||||
try {
|
|
||||||
WebView(activity).run {
|
|
||||||
setDefaultSettings()
|
|
||||||
clearCache(true)
|
|
||||||
clearFormData()
|
|
||||||
clearHistory()
|
|
||||||
clearSslPreferences()
|
|
||||||
}
|
|
||||||
WebStorage.getInstance().deleteAllData()
|
|
||||||
activity.applicationInfo?.dataDir?.let { File("$it/app_webview/").deleteRecursively() }
|
|
||||||
activity.toast(R.string.webview_data_deleted)
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
logcat(LogPriority.ERROR, e)
|
|
||||||
activity.toast(R.string.cache_delete_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetViewerFlags() {
|
|
||||||
val activity = activity ?: return
|
|
||||||
viewScope.launchNonCancellable {
|
|
||||||
val success = mangaRepository.resetViewerFlags()
|
|
||||||
withUIContext {
|
|
||||||
val message = if (success) {
|
|
||||||
R.string.pref_reset_viewer_flags_success
|
|
||||||
} else {
|
|
||||||
R.string.pref_reset_viewer_flags_error
|
|
||||||
}
|
|
||||||
activity.toast(message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val CLEAR_CACHE_KEY = "pref_clear_cache_key"
|
|
|
@ -1,173 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.core.app.ActivityCompat
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
|
||||||
import eu.kanade.domain.ui.model.ThemeMode
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.initThenAdd
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.listPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.isDynamicColorAvailable
|
|
||||||
import eu.kanade.tachiyomi.util.system.isTablet
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.ThemesPreference
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class SettingsAppearanceController : SettingsController() {
|
|
||||||
|
|
||||||
private var themesPreference: ThemesPreference? = null
|
|
||||||
private val uiPreferences: UiPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_appearance
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_theme
|
|
||||||
|
|
||||||
listPreference {
|
|
||||||
bindTo(uiPreferences.themeMode())
|
|
||||||
titleRes = R.string.pref_theme_mode
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.theme_system,
|
|
||||||
R.string.theme_light,
|
|
||||||
R.string.theme_dark,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf(
|
|
||||||
ThemeMode.SYSTEM.name,
|
|
||||||
ThemeMode.LIGHT.name,
|
|
||||||
ThemeMode.DARK.name,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.theme_light,
|
|
||||||
R.string.theme_dark,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf(
|
|
||||||
ThemeMode.LIGHT.name,
|
|
||||||
ThemeMode.DARK.name,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
themesPreference = initThenAdd(ThemesPreference(context)) {
|
|
||||||
bindTo(uiPreferences.appTheme())
|
|
||||||
titleRes = R.string.pref_app_theme
|
|
||||||
|
|
||||||
val appThemes = AppTheme.values().filter {
|
|
||||||
val monetFilter = if (it == AppTheme.MONET) {
|
|
||||||
DeviceUtil.isDynamicColorAvailable
|
|
||||||
} else {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
it.titleResId != null && monetFilter
|
|
||||||
}
|
|
||||||
entries = appThemes
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
activity?.let { ActivityCompat.recreate(it) }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(uiPreferences.themeDarkAmoled())
|
|
||||||
titleRes = R.string.pref_dark_theme_pure_black
|
|
||||||
|
|
||||||
visibleIf(uiPreferences.themeMode()) { it != ThemeMode.LIGHT }
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
activity?.let { ActivityCompat.recreate(it) }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.isTablet()) {
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_navigation
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(uiPreferences.sideNavIconAlignment())
|
|
||||||
titleRes = R.string.pref_side_nav_icon_alignment
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.alignment_top,
|
|
||||||
R.string.alignment_center,
|
|
||||||
R.string.alignment_bottom,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("0", "1", "2")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_timestamps
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(uiPreferences.relativeTime())
|
|
||||||
titleRes = R.string.pref_relative_format
|
|
||||||
val values = arrayOf("0", "2", "7")
|
|
||||||
entryValues = values
|
|
||||||
entries = values.map {
|
|
||||||
when (it) {
|
|
||||||
"0" -> context.getString(R.string.off)
|
|
||||||
"2" -> context.getString(R.string.pref_relative_time_short)
|
|
||||||
else -> context.getString(R.string.pref_relative_time_long)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
|
|
||||||
listPreference {
|
|
||||||
bindTo(uiPreferences.dateFormat())
|
|
||||||
titleRes = R.string.pref_date_format
|
|
||||||
entryValues = arrayOf("", "MM/dd/yy", "dd/MM/yy", "yyyy-MM-dd", "dd MMM yyyy", "MMM dd, yyyy")
|
|
||||||
|
|
||||||
val now = Date().time
|
|
||||||
entries = entryValues.map { value ->
|
|
||||||
val formattedDate = UiPreferences.dateFormat(value.toString()).format(now)
|
|
||||||
if (value == "") {
|
|
||||||
"${context.getString(R.string.label_default)} ($formattedDate)"
|
|
||||||
} else {
|
|
||||||
"$value ($formattedDate)"
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveViewState(view: View, outState: Bundle) {
|
|
||||||
themesPreference?.let {
|
|
||||||
outState.putInt(THEMES_SCROLL_POSITION, it.lastScrollPosition ?: 0)
|
|
||||||
}
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRestoreViewState(view: View, savedViewState: Bundle) {
|
|
||||||
super.onRestoreViewState(view, savedViewState)
|
|
||||||
themesPreference?.lastScrollPosition = savedViewState.getInt(THEMES_SCROLL_POSITION, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
themesPreference = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val THEMES_SCROLL_POSITION = "themesScrollPosition"
|
|
|
@ -1,306 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.Manifest.permission.WRITE_EXTERNAL_STORAGE
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.domain.backup.service.BackupPreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupConst
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
|
|
||||||
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
|
|
||||||
import eu.kanade.tachiyomi.data.backup.models.Backup
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.infoPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.getParcelableCompat
|
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsBackupController : SettingsController() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flags containing information of what to backup.
|
|
||||||
*/
|
|
||||||
private var backupFlags = 0
|
|
||||||
|
|
||||||
private val backupPreferences: BackupPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
||||||
super.onViewCreated(view, savedInstanceState)
|
|
||||||
requestPermissionsSafe(arrayOf(WRITE_EXTERNAL_STORAGE), 500)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.label_backup
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_create_backup"
|
|
||||||
titleRes = R.string.pref_create_backup
|
|
||||||
summaryRes = R.string.pref_create_backup_summ
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
||||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BackupCreatorJob.isManualJobRunning(context)) {
|
|
||||||
val ctrl = CreateBackupDialog()
|
|
||||||
ctrl.targetController = this@SettingsBackupController
|
|
||||||
ctrl.showDialog(router)
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.backup_in_progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
key = "pref_restore_backup"
|
|
||||||
titleRes = R.string.pref_restore_backup
|
|
||||||
summaryRes = R.string.pref_restore_backup_summ
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
|
|
||||||
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!BackupRestoreService.isRunning(context)) {
|
|
||||||
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
|
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
type = "*/*"
|
|
||||||
}
|
|
||||||
val title = resources?.getString(R.string.file_select_backup)
|
|
||||||
val chooser = Intent.createChooser(intent, title)
|
|
||||||
startActivityForResult(chooser, CODE_BACKUP_RESTORE)
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.restore_in_progress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_backup_service_category
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(backupPreferences.backupInterval())
|
|
||||||
titleRes = R.string.pref_backup_interval
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.update_6hour,
|
|
||||||
R.string.update_12hour,
|
|
||||||
R.string.update_24hour,
|
|
||||||
R.string.update_48hour,
|
|
||||||
R.string.update_weekly,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("6", "12", "24", "48", "168")
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
onChange { newValue ->
|
|
||||||
val interval = (newValue as String).toInt()
|
|
||||||
BackupCreatorJob.setupTask(context, interval)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
bindTo(backupPreferences.backupsDirectory())
|
|
||||||
titleRes = R.string.pref_backup_directory
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
try {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
startActivityForResult(intent, CODE_BACKUP_DIR)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
activity?.toast(R.string.file_picker_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
backupPreferences.backupsDirectory().changes()
|
|
||||||
.onEach { path ->
|
|
||||||
val dir = UniFile.fromUri(context, path.toUri())
|
|
||||||
summary = dir.filePath + "/automatic"
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(backupPreferences.numberOfBackups())
|
|
||||||
titleRes = R.string.pref_backup_slots
|
|
||||||
entries = arrayOf("2", "3", "4", "5")
|
|
||||||
entryValues = entries
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoPreference(R.string.backup_info)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.settings_backup, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_backup_help -> activity?.openInBrowser(HELP_URL)
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
if (data != null && resultCode == Activity.RESULT_OK) {
|
|
||||||
val activity = activity ?: return
|
|
||||||
val uri = data.data
|
|
||||||
|
|
||||||
if (uri == null) {
|
|
||||||
activity.toast(R.string.backup_restore_invalid_uri)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
when (requestCode) {
|
|
||||||
CODE_BACKUP_DIR -> {
|
|
||||||
// Get UriPermission so it's possible to write files
|
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
|
|
||||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
|
||||||
backupPreferences.backupsDirectory().set(uri.toString())
|
|
||||||
}
|
|
||||||
CODE_BACKUP_CREATE -> {
|
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
|
|
||||||
activity.contentResolver.takePersistableUriPermission(uri, flags)
|
|
||||||
BackupCreatorJob.startNow(activity, uri, backupFlags)
|
|
||||||
}
|
|
||||||
CODE_BACKUP_RESTORE -> {
|
|
||||||
RestoreBackupDialog(uri).showDialog(router)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createBackup(flags: Int) {
|
|
||||||
backupFlags = flags
|
|
||||||
try {
|
|
||||||
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
|
|
||||||
.addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
.setType("application/*")
|
|
||||||
.putExtra(Intent.EXTRA_TITLE, Backup.getBackupFilename())
|
|
||||||
|
|
||||||
startActivityForResult(intent, CODE_BACKUP_CREATE)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
activity?.toast(R.string.file_picker_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CreateBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val activity = activity!!
|
|
||||||
val options = arrayOf(
|
|
||||||
R.string.manga,
|
|
||||||
R.string.categories,
|
|
||||||
R.string.chapters,
|
|
||||||
R.string.track,
|
|
||||||
R.string.history,
|
|
||||||
)
|
|
||||||
.map { activity.getString(it) }
|
|
||||||
val selected = options.map { true }.toBooleanArray()
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.backup_choice)
|
|
||||||
.setMultiChoiceItems(options.toTypedArray(), selected) { dialog, which, checked ->
|
|
||||||
if (which == 0) {
|
|
||||||
(dialog as AlertDialog).listView.setItemChecked(which, true)
|
|
||||||
} else {
|
|
||||||
selected[which] = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.setPositiveButton(R.string.action_create) { _, _ ->
|
|
||||||
var flags = 0
|
|
||||||
selected.forEachIndexed { i, checked ->
|
|
||||||
if (checked) {
|
|
||||||
when (i) {
|
|
||||||
1 -> flags = flags or BackupConst.BACKUP_CATEGORY
|
|
||||||
2 -> flags = flags or BackupConst.BACKUP_CHAPTER
|
|
||||||
3 -> flags = flags or BackupConst.BACKUP_TRACK
|
|
||||||
4 -> flags = flags or BackupConst.BACKUP_HISTORY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(targetController as? SettingsBackupController)?.createBackup(flags)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RestoreBackupDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|
||||||
constructor(uri: Uri) : this(
|
|
||||||
bundleOf(KEY_URI to uri),
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val activity = activity!!
|
|
||||||
val uri = args.getParcelableCompat<Uri>(KEY_URI)!!
|
|
||||||
|
|
||||||
return try {
|
|
||||||
val results = BackupFileValidator().validate(activity, uri)
|
|
||||||
|
|
||||||
var message = activity.getString(R.string.backup_restore_content_full)
|
|
||||||
if (results.missingSources.isNotEmpty()) {
|
|
||||||
message += "\n\n${activity.getString(R.string.backup_restore_missing_sources)}\n${results.missingSources.joinToString("\n") { "- $it" }}"
|
|
||||||
}
|
|
||||||
if (results.missingTrackers.isNotEmpty()) {
|
|
||||||
message += "\n\n${activity.getString(R.string.backup_restore_missing_trackers)}\n${results.missingTrackers.joinToString("\n") { "- $it" }}"
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.pref_restore_backup)
|
|
||||||
.setMessage(message)
|
|
||||||
.setPositiveButton(R.string.action_restore) { _, _ ->
|
|
||||||
BackupRestoreService.start(activity, uri)
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.invalid_backup_file)
|
|
||||||
.setMessage(e.message)
|
|
||||||
.setPositiveButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val KEY_URI = "RestoreBackupDialog.uri"
|
|
||||||
|
|
||||||
private const val CODE_BACKUP_DIR = 503
|
|
||||||
private const val CODE_BACKUP_CREATE = 504
|
|
||||||
private const val CODE_BACKUP_RESTORE = 505
|
|
||||||
|
|
||||||
private const val HELP_URL = "https://tachiyomi.org/help/guides/backups/"
|
|
|
@ -1,80 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.domain.source.service.SourcePreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.infoPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.requireAuthentication
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsBrowseController : SettingsController() {
|
|
||||||
|
|
||||||
private val sourcePreferences: SourcePreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.browse
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_sources
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(sourcePreferences.duplicatePinnedSources())
|
|
||||||
titleRes = R.string.pref_duplicate_pinned_sources
|
|
||||||
summaryRes = R.string.pref_duplicate_pinned_sources_summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.label_extensions
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(preferences.automaticExtUpdates())
|
|
||||||
titleRes = R.string.pref_enable_automatic_extension_updates
|
|
||||||
|
|
||||||
onChange { newValue ->
|
|
||||||
val checked = newValue as Boolean
|
|
||||||
ExtensionUpdateJob.setupTask(activity!!, checked)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.action_global_search
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(sourcePreferences.searchPinnedSourcesOnly())
|
|
||||||
titleRes = R.string.pref_search_pinned_sources_only
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_nsfw_content
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(sourcePreferences.showNsfwSource())
|
|
||||||
titleRes = R.string.pref_show_nsfw_source
|
|
||||||
summaryRes = R.string.requires_app_restart
|
|
||||||
|
|
||||||
if (context.isAuthenticationSupported() && activity != null) {
|
|
||||||
requireAuthentication(
|
|
||||||
activity as? FragmentActivity,
|
|
||||||
context.getString(R.string.pref_category_nsfw_content),
|
|
||||||
context.getString(R.string.confirm_lock_change),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoPreference(R.string.parental_controls_info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.animation.ArgbEvaluator
|
|
||||||
import android.animation.ValueAnimator
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
|
||||||
import androidx.core.animation.doOnEnd
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceController
|
|
||||||
import androidx.preference.PreferenceGroup
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import dev.chrisbanes.insetter.applyInsetter
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.preference.asHotFlow
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.MainScope
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
abstract class SettingsController : PreferenceController() {
|
|
||||||
|
|
||||||
var preferenceKey: String? = null
|
|
||||||
val preferences: BasePreferences = Injekt.get()
|
|
||||||
val viewScope: CoroutineScope = MainScope()
|
|
||||||
private var themedContext: Context? = null
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup, savedInstanceState: Bundle?): View {
|
|
||||||
val view = super.onCreateView(inflater, container, savedInstanceState)
|
|
||||||
|
|
||||||
listView.applyInsetter {
|
|
||||||
type(navigationBars = true) {
|
|
||||||
padding()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return view
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(view: View) {
|
|
||||||
super.onAttach(view)
|
|
||||||
|
|
||||||
preferenceKey?.let { prefKey ->
|
|
||||||
val adapter = listView.adapter
|
|
||||||
scrollToPreference(prefKey)
|
|
||||||
|
|
||||||
listView.post {
|
|
||||||
if (adapter is PreferenceGroup.PreferencePositionCallback) {
|
|
||||||
val pos = adapter.getPreferenceAdapterPosition(prefKey)
|
|
||||||
listView.findViewHolderForAdapterPosition(pos)?.let {
|
|
||||||
animatePreferenceHighlight(it.itemView)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Explicitly clear it to avoid re-scrolling/animating on activity recreations
|
|
||||||
preferenceKey = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
if (type.isEnter) {
|
|
||||||
setTitle()
|
|
||||||
}
|
|
||||||
setHasOptionsMenu(type.isEnter)
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
|
||||||
super.onDestroyView(view)
|
|
||||||
viewScope.cancel()
|
|
||||||
themedContext = null
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
|
||||||
val tv = TypedValue()
|
|
||||||
activity!!.theme.resolveAttribute(R.attr.preferenceTheme, tv, true)
|
|
||||||
themedContext = ContextThemeWrapper(activity, tv.resourceId)
|
|
||||||
|
|
||||||
val screen = preferenceManager.createPreferenceScreen(themedContext!!)
|
|
||||||
preferenceScreen = screen
|
|
||||||
setupPreferenceScreen(screen)
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract fun setupPreferenceScreen(screen: PreferenceScreen): PreferenceScreen
|
|
||||||
|
|
||||||
private fun animatePreferenceHighlight(view: View) {
|
|
||||||
val origBackground = view.background
|
|
||||||
ValueAnimator
|
|
||||||
.ofObject(ArgbEvaluator(), Color.TRANSPARENT, view.context.getResourceColor(R.attr.colorControlHighlight))
|
|
||||||
.apply {
|
|
||||||
duration = 200L
|
|
||||||
repeatCount = 5
|
|
||||||
repeatMode = ValueAnimator.REVERSE
|
|
||||||
addUpdateListener { animator -> view.setBackgroundColor(animator.animatedValue as Int) }
|
|
||||||
start()
|
|
||||||
}
|
|
||||||
.doOnEnd {
|
|
||||||
// Restore original ripple
|
|
||||||
view.background = origBackground
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setTitle() {
|
|
||||||
(activity as? AppCompatActivity)?.supportActionBar?.title = preferenceScreen?.title?.toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Preference.visibleIf(preference: eu.kanade.tachiyomi.core.preference.Preference<T>, crossinline block: (T) -> Boolean) {
|
|
||||||
preference.asHotFlow { isVisible = block(it) }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,315 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.content.ActivityNotFoundException
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Environment
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import androidx.core.text.buildSpannedString
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.hippo.unifile.UniFile
|
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
|
||||||
import eu.kanade.presentation.category.visualName
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.infoPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.multiSelectListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class SettingsDownloadController : SettingsController() {
|
|
||||||
|
|
||||||
private val getCategories: GetCategories by injectLazy()
|
|
||||||
private val downloadPreferences: DownloadPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_downloads
|
|
||||||
|
|
||||||
val categories = runBlocking { getCategories.await() }
|
|
||||||
|
|
||||||
preference {
|
|
||||||
bindTo(downloadPreferences.downloadsDirectory())
|
|
||||||
titleRes = R.string.pref_download_directory
|
|
||||||
onClick {
|
|
||||||
val ctrl = DownloadDirectoriesDialog()
|
|
||||||
ctrl.targetController = this@SettingsDownloadController
|
|
||||||
ctrl.showDialog(router)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadPreferences.downloadsDirectory().changes()
|
|
||||||
.onEach { path ->
|
|
||||||
val dir = UniFile.fromUri(context, path.toUri())
|
|
||||||
summary = dir.filePath ?: path
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.downloadOnlyOverWifi())
|
|
||||||
titleRes = R.string.connected_to_wifi
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.saveChaptersAsCBZ())
|
|
||||||
titleRes = R.string.save_chapter_as_cbz
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.splitTallImages())
|
|
||||||
titleRes = R.string.split_tall_images
|
|
||||||
summaryRes = R.string.split_tall_images_summary
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_delete_chapters
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.removeAfterMarkedAsRead())
|
|
||||||
titleRes = R.string.pref_remove_after_marked_as_read
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(downloadPreferences.removeAfterReadSlots())
|
|
||||||
titleRes = R.string.pref_remove_after_read
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.disabled,
|
|
||||||
R.string.last_read_chapter,
|
|
||||||
R.string.second_to_last,
|
|
||||||
R.string.third_to_last,
|
|
||||||
R.string.fourth_to_last,
|
|
||||||
R.string.fifth_to_last,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("-1", "0", "1", "2", "3", "4")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.removeBookmarkedChapters())
|
|
||||||
titleRes = R.string.pref_remove_bookmarked_chapters
|
|
||||||
}
|
|
||||||
multiSelectListPreference {
|
|
||||||
bindTo(downloadPreferences.removeExcludeCategories())
|
|
||||||
titleRes = R.string.pref_remove_exclude_categories
|
|
||||||
entries = categories.map { it.visualName(context) }.toTypedArray()
|
|
||||||
entryValues = categories.map { it.id.toString() }.toTypedArray()
|
|
||||||
|
|
||||||
downloadPreferences.removeExcludeCategories().changes()
|
|
||||||
.onEach { mutable ->
|
|
||||||
val selected = mutable
|
|
||||||
.mapNotNull { id -> categories.find { it.id == id.toLong() } }
|
|
||||||
.sortedBy { it.order }
|
|
||||||
|
|
||||||
summary = if (selected.isEmpty()) {
|
|
||||||
resources?.getString(R.string.none)
|
|
||||||
} else {
|
|
||||||
selected.joinToString { it.visualName(context) }
|
|
||||||
}
|
|
||||||
}.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_download_new
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(downloadPreferences.downloadNewChapters())
|
|
||||||
titleRes = R.string.pref_download_new
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
bindTo(downloadPreferences.downloadNewChapterCategories())
|
|
||||||
titleRes = R.string.categories
|
|
||||||
onClick {
|
|
||||||
DownloadCategoriesDialog().showDialog(router)
|
|
||||||
}
|
|
||||||
|
|
||||||
visibleIf(downloadPreferences.downloadNewChapters()) { it }
|
|
||||||
|
|
||||||
fun updateSummary() {
|
|
||||||
val selectedCategories = downloadPreferences.downloadNewChapterCategories().get()
|
|
||||||
.mapNotNull { id -> categories.find { it.id == id.toLong() } }
|
|
||||||
.sortedBy { it.order }
|
|
||||||
val includedItemsText = if (selectedCategories.isEmpty()) {
|
|
||||||
context.getString(R.string.all)
|
|
||||||
} else {
|
|
||||||
selectedCategories.joinToString { it.visualName(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val excludedCategories = downloadPreferences.downloadNewChapterCategoriesExclude().get()
|
|
||||||
.mapNotNull { id -> categories.find { it.id == id.toLong() } }
|
|
||||||
.sortedBy { it.order }
|
|
||||||
val excludedItemsText = if (excludedCategories.isEmpty()) {
|
|
||||||
context.getString(R.string.none)
|
|
||||||
} else {
|
|
||||||
excludedCategories.joinToString { it.visualName(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
summary = buildSpannedString {
|
|
||||||
append(context.getString(R.string.include, includedItemsText))
|
|
||||||
appendLine()
|
|
||||||
append(context.getString(R.string.exclude, excludedItemsText))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadPreferences.downloadNewChapterCategories().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
downloadPreferences.downloadNewChapterCategoriesExclude().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.download_ahead
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(downloadPreferences.autoDownloadWhileReading())
|
|
||||||
titleRes = R.string.auto_download_while_reading
|
|
||||||
entries = arrayOf(
|
|
||||||
context.getString(R.string.disabled),
|
|
||||||
context.resources.getQuantityString(R.plurals.next_unread_chapters, 2, 2),
|
|
||||||
context.resources.getQuantityString(R.plurals.next_unread_chapters, 3, 3),
|
|
||||||
context.resources.getQuantityString(R.plurals.next_unread_chapters, 5, 5),
|
|
||||||
context.resources.getQuantityString(R.plurals.next_unread_chapters, 10, 10),
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("0", "2", "3", "5", "10")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
infoPreference(R.string.download_ahead_info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when (requestCode) {
|
|
||||||
DOWNLOAD_DIR -> if (data != null && resultCode == Activity.RESULT_OK) {
|
|
||||||
val context = applicationContext ?: return
|
|
||||||
val uri = data.data
|
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
|
||||||
|
|
||||||
if (uri != null) {
|
|
||||||
@Suppress("NewApi")
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
val file = UniFile.fromUri(context, uri)
|
|
||||||
downloadPreferences.downloadsDirectory().set(file.uri.toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun predefinedDirectorySelected(selectedDir: String) {
|
|
||||||
val path = File(selectedDir).toUri()
|
|
||||||
downloadPreferences.downloadsDirectory().set(path.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun customDirectorySelected() {
|
|
||||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
|
||||||
try {
|
|
||||||
startActivityForResult(intent, DOWNLOAD_DIR)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
activity?.toast(R.string.file_picker_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadDirectoriesDialog : DialogController() {
|
|
||||||
|
|
||||||
private val downloadPreferences: DownloadPreferences = Injekt.get()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val activity = activity!!
|
|
||||||
val currentDir = downloadPreferences.downloadsDirectory().get()
|
|
||||||
val externalDirs = listOf(getDefaultDownloadDir(), File(activity.getString(R.string.custom_dir))).map(File::toString)
|
|
||||||
var selectedIndex = externalDirs.indexOfFirst { it in currentDir }
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(activity)
|
|
||||||
.setTitle(R.string.pref_download_directory)
|
|
||||||
.setSingleChoiceItems(externalDirs.toTypedArray(), selectedIndex) { _, which ->
|
|
||||||
selectedIndex = which
|
|
||||||
}
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val target = targetController as? SettingsDownloadController
|
|
||||||
if (selectedIndex == externalDirs.lastIndex) {
|
|
||||||
target?.customDirectorySelected()
|
|
||||||
} else {
|
|
||||||
target?.predefinedDirectorySelected(externalDirs[selectedIndex])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getDefaultDownloadDir(): File {
|
|
||||||
val defaultDir = Environment.getExternalStorageDirectory().absolutePath +
|
|
||||||
File.separator + resources?.getString(R.string.app_name) +
|
|
||||||
File.separator + "downloads"
|
|
||||||
|
|
||||||
return File(defaultDir)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DownloadCategoriesDialog : DialogController() {
|
|
||||||
|
|
||||||
private val downloadPreferences: DownloadPreferences = Injekt.get()
|
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val categories = runBlocking { getCategories.await() }
|
|
||||||
|
|
||||||
val items = categories.map { it.visualName(activity!!) }
|
|
||||||
var selected = categories
|
|
||||||
.map {
|
|
||||||
when (it.id.toString()) {
|
|
||||||
in downloadPreferences.downloadNewChapterCategories().get() -> QuadStateTextView.State.CHECKED.ordinal
|
|
||||||
in downloadPreferences.downloadNewChapterCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal
|
|
||||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toIntArray()
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.categories)
|
|
||||||
.setQuadStateMultiChoiceItems(
|
|
||||||
message = R.string.pref_download_new_categories_details,
|
|
||||||
items = items,
|
|
||||||
initialSelected = selected,
|
|
||||||
) { selections ->
|
|
||||||
selected = selections
|
|
||||||
}
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val included = selected
|
|
||||||
.mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
|
|
||||||
.filterNotNull()
|
|
||||||
.map { categories[it].id.toString() }
|
|
||||||
.toSet()
|
|
||||||
val excluded = selected
|
|
||||||
.mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
|
|
||||||
.filterNotNull()
|
|
||||||
.map { categories[it].id.toString() }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
downloadPreferences.downloadNewChapterCategories().set(included)
|
|
||||||
downloadPreferences.downloadNewChapterCategoriesExclude().set(excluded)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val DOWNLOAD_DIR = 104
|
|
|
@ -1,92 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.core.os.LocaleListCompat
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.listPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
|
||||||
import org.xmlpull.v1.XmlPullParser
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsGeneralController : SettingsController() {
|
|
||||||
|
|
||||||
private val libraryPreferences: LibraryPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_general
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(libraryPreferences.showUpdatesNavBadge())
|
|
||||||
titleRes = R.string.pref_library_update_show_tab_badge
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(preferences.confirmExit())
|
|
||||||
titleRes = R.string.pref_confirm_exit
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
preference {
|
|
||||||
key = "pref_manage_notifications"
|
|
||||||
titleRes = R.string.pref_manage_notifications
|
|
||||||
onClick {
|
|
||||||
val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
|
||||||
putExtra(Settings.EXTRA_APP_PACKAGE, context.packageName)
|
|
||||||
}
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
listPreference {
|
|
||||||
key = "app_lang"
|
|
||||||
isPersistent = false
|
|
||||||
titleRes = R.string.pref_app_language
|
|
||||||
|
|
||||||
val langs = mutableListOf<Pair<String, String>>()
|
|
||||||
|
|
||||||
val parser = context.resources.getXml(R.xml.locales_config)
|
|
||||||
var eventType = parser.eventType
|
|
||||||
while (eventType != XmlPullParser.END_DOCUMENT) {
|
|
||||||
if (eventType == XmlPullParser.START_TAG && parser.name == "locale") {
|
|
||||||
for (i in 0 until parser.attributeCount) {
|
|
||||||
if (parser.getAttributeName(i) == "name") {
|
|
||||||
val langTag = parser.getAttributeValue(i)
|
|
||||||
val displayName = LocaleHelper.getDisplayName(langTag)
|
|
||||||
if (displayName.isNotEmpty()) {
|
|
||||||
langs.add(Pair(langTag, displayName))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventType = parser.next()
|
|
||||||
}
|
|
||||||
|
|
||||||
langs.sortBy { it.second }
|
|
||||||
langs.add(0, Pair("", context.getString(R.string.label_default)))
|
|
||||||
|
|
||||||
entryValues = langs.map { it.first }.toTypedArray()
|
|
||||||
entries = langs.map { it.second }.toTypedArray()
|
|
||||||
summary = "%s"
|
|
||||||
value = AppCompatDelegate.getApplicationLocales().get(0)?.toLanguageTag() ?: ""
|
|
||||||
|
|
||||||
onChange { newValue ->
|
|
||||||
val locale = if ((newValue as String).isEmpty()) {
|
|
||||||
LocaleListCompat.getEmptyLocaleList()
|
|
||||||
} else {
|
|
||||||
LocaleListCompat.forLanguageTags(newValue)
|
|
||||||
}
|
|
||||||
AppCompatDelegate.setApplicationLocales(locale)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,388 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.text.buildSpannedString
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
|
||||||
import eu.kanade.domain.category.interactor.ResetCategoryFlags
|
|
||||||
import eu.kanade.domain.category.model.Category
|
|
||||||
import eu.kanade.domain.library.service.LibraryPreferences
|
|
||||||
import eu.kanade.presentation.category.visualName
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_BATTERY_NOT_LOW
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_NETWORK_NOT_METERED
|
|
||||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED
|
|
||||||
import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
import eu.kanade.tachiyomi.ui.category.CategoryController
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.defaultValue
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.multiSelectListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onChange
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
|
||||||
import eu.kanade.tachiyomi.widget.materialdialogs.setQuadStateMultiChoiceItems
|
|
||||||
import kotlinx.coroutines.flow.combine
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
|
||||||
import kotlinx.coroutines.flow.onEach
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsLibraryController : SettingsController() {
|
|
||||||
|
|
||||||
private val getCategories: GetCategories by injectLazy()
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
|
||||||
private val resetCategoryFlags: ResetCategoryFlags by injectLazy()
|
|
||||||
private val libraryPreferences: LibraryPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_library
|
|
||||||
|
|
||||||
val allCategories = runBlocking { getCategories.await() }
|
|
||||||
val userCategories = allCategories.filterNot(Category::isSystemCategory)
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_display
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_library_columns"
|
|
||||||
titleRes = R.string.pref_library_columns
|
|
||||||
onClick {
|
|
||||||
LibraryColumnsDialog().showDialog(router)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getColumnValue(value: Int): String {
|
|
||||||
return if (value == 0) {
|
|
||||||
context.getString(R.string.label_default)
|
|
||||||
} else {
|
|
||||||
value.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
combine(libraryPreferences.portraitColumns().changes(), libraryPreferences.landscapeColumns().changes()) { portraitCols, landscapeCols -> Pair(portraitCols, landscapeCols) }
|
|
||||||
.onEach { (portraitCols, landscapeCols) ->
|
|
||||||
val portrait = getColumnValue(portraitCols)
|
|
||||||
val landscape = getColumnValue(landscapeCols)
|
|
||||||
summary = "${context.getString(R.string.portrait)}: $portrait, " +
|
|
||||||
"${context.getString(R.string.landscape)}: $landscape"
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.categories
|
|
||||||
|
|
||||||
preference {
|
|
||||||
key = "pref_action_edit_categories"
|
|
||||||
titleRes = R.string.action_edit_categories
|
|
||||||
|
|
||||||
val catCount = userCategories.size
|
|
||||||
summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount)
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
router.pushController(CategoryController())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
val defaultCategory = libraryPreferences.defaultCategory()
|
|
||||||
bindTo(defaultCategory)
|
|
||||||
titleRes = R.string.default_category
|
|
||||||
|
|
||||||
entries = arrayOf(context.getString(R.string.default_category_summary)) +
|
|
||||||
allCategories.map { it.visualName(context) }.toTypedArray()
|
|
||||||
entryValues = arrayOf(defaultCategory.defaultValue().toString()) + allCategories.map { it.id.toString() }.toTypedArray()
|
|
||||||
|
|
||||||
val selectedCategory = allCategories.find { it.id == defaultCategory.get().toLong() }
|
|
||||||
summary = selectedCategory?.visualName(context)
|
|
||||||
?: context.getString(R.string.default_category_summary)
|
|
||||||
onChange { newValue ->
|
|
||||||
summary = allCategories.find {
|
|
||||||
it.id == (newValue as String).toLong()
|
|
||||||
}?.visualName(context) ?: context.getString(R.string.default_category_summary)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(libraryPreferences.categorizedDisplaySettings())
|
|
||||||
titleRes = R.string.categorized_display_settings
|
|
||||||
|
|
||||||
libraryPreferences.categorizedDisplaySettings().changes()
|
|
||||||
.onEach {
|
|
||||||
if (it.not()) {
|
|
||||||
resetCategoryFlags.await()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_library_update
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(libraryPreferences.libraryUpdateInterval())
|
|
||||||
titleRes = R.string.pref_library_update_interval
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.update_never,
|
|
||||||
R.string.update_12hour,
|
|
||||||
R.string.update_24hour,
|
|
||||||
R.string.update_48hour,
|
|
||||||
R.string.update_72hour,
|
|
||||||
R.string.update_weekly,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("0", "12", "24", "48", "72", "168")
|
|
||||||
summary = "%s"
|
|
||||||
|
|
||||||
onChange { newValue ->
|
|
||||||
val interval = (newValue as String).toInt()
|
|
||||||
LibraryUpdateJob.setupTask(context, interval)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
multiSelectListPreference {
|
|
||||||
bindTo(libraryPreferences.libraryUpdateDeviceRestriction())
|
|
||||||
titleRes = R.string.pref_library_update_restriction
|
|
||||||
entriesRes = arrayOf(R.string.connected_to_wifi, R.string.network_not_metered, R.string.charging, R.string.battery_not_low)
|
|
||||||
entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_NETWORK_NOT_METERED, DEVICE_CHARGING, DEVICE_BATTERY_NOT_LOW)
|
|
||||||
|
|
||||||
visibleIf(libraryPreferences.libraryUpdateInterval()) { it > 0 }
|
|
||||||
|
|
||||||
onChange {
|
|
||||||
// Post to event looper to allow the preference to be updated.
|
|
||||||
ContextCompat.getMainExecutor(context).execute { LibraryUpdateJob.setupTask(context) }
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateSummary() {
|
|
||||||
val restrictions = libraryPreferences.libraryUpdateDeviceRestriction().get()
|
|
||||||
.sorted()
|
|
||||||
.map {
|
|
||||||
when (it) {
|
|
||||||
DEVICE_ONLY_ON_WIFI -> context.getString(R.string.connected_to_wifi)
|
|
||||||
DEVICE_NETWORK_NOT_METERED -> context.getString(R.string.network_not_metered)
|
|
||||||
DEVICE_CHARGING -> context.getString(R.string.charging)
|
|
||||||
DEVICE_BATTERY_NOT_LOW -> context.getString(R.string.battery_not_low)
|
|
||||||
else -> it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val restrictionsText = if (restrictions.isEmpty()) {
|
|
||||||
context.getString(R.string.none)
|
|
||||||
} else {
|
|
||||||
restrictions.joinToString()
|
|
||||||
}
|
|
||||||
|
|
||||||
summary = context.getString(R.string.restrictions, restrictionsText)
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryPreferences.libraryUpdateDeviceRestriction().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
multiSelectListPreference {
|
|
||||||
bindTo(libraryPreferences.libraryUpdateMangaRestriction())
|
|
||||||
titleRes = R.string.pref_library_update_manga_restriction
|
|
||||||
entriesRes = arrayOf(R.string.pref_update_only_completely_read, R.string.pref_update_only_started, R.string.pref_update_only_non_completed)
|
|
||||||
entryValues = arrayOf(MANGA_HAS_UNREAD, MANGA_NON_READ, MANGA_NON_COMPLETED)
|
|
||||||
|
|
||||||
fun updateSummary() {
|
|
||||||
val restrictions = libraryPreferences.libraryUpdateMangaRestriction().get().sorted()
|
|
||||||
.map {
|
|
||||||
when (it) {
|
|
||||||
MANGA_NON_READ -> context.getString(R.string.pref_update_only_started)
|
|
||||||
MANGA_HAS_UNREAD -> context.getString(R.string.pref_update_only_completely_read)
|
|
||||||
MANGA_NON_COMPLETED -> context.getString(R.string.pref_update_only_non_completed)
|
|
||||||
else -> it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val restrictionsText = if (restrictions.isEmpty()) {
|
|
||||||
context.getString(R.string.none)
|
|
||||||
} else {
|
|
||||||
restrictions.joinToString()
|
|
||||||
}
|
|
||||||
|
|
||||||
summary = restrictionsText
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryPreferences.libraryUpdateMangaRestriction().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
preference {
|
|
||||||
bindTo(libraryPreferences.libraryUpdateCategories())
|
|
||||||
titleRes = R.string.categories
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
LibraryGlobalUpdateCategoriesDialog().showDialog(router)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateSummary() {
|
|
||||||
val includedCategories = libraryPreferences.libraryUpdateCategories().get()
|
|
||||||
.mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
|
|
||||||
.sortedBy { it.order }
|
|
||||||
val excludedCategories = libraryPreferences.libraryUpdateCategoriesExclude().get()
|
|
||||||
.mapNotNull { id -> allCategories.find { it.id == id.toLong() } }
|
|
||||||
.sortedBy { it.order }
|
|
||||||
|
|
||||||
val allExcluded = excludedCategories.size == allCategories.size
|
|
||||||
|
|
||||||
val includedItemsText = when {
|
|
||||||
// Some selected, but not all
|
|
||||||
includedCategories.isNotEmpty() && includedCategories.size != allCategories.size -> includedCategories.joinToString { it.visualName(context) }
|
|
||||||
// All explicitly selected
|
|
||||||
includedCategories.size == allCategories.size -> context.getString(R.string.all)
|
|
||||||
allExcluded -> context.getString(R.string.none)
|
|
||||||
else -> context.getString(R.string.all)
|
|
||||||
}
|
|
||||||
val excludedItemsText = when {
|
|
||||||
excludedCategories.isEmpty() -> context.getString(R.string.none)
|
|
||||||
allExcluded -> context.getString(R.string.all)
|
|
||||||
else -> excludedCategories.joinToString { it.visualName(context) }
|
|
||||||
}
|
|
||||||
|
|
||||||
summary = buildSpannedString {
|
|
||||||
append(context.getString(R.string.include, includedItemsText))
|
|
||||||
appendLine()
|
|
||||||
append(context.getString(R.string.exclude, excludedItemsText))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryPreferences.libraryUpdateCategories().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
libraryPreferences.libraryUpdateCategoriesExclude().changes()
|
|
||||||
.onEach { updateSummary() }
|
|
||||||
.launchIn(viewScope)
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(libraryPreferences.autoUpdateMetadata())
|
|
||||||
titleRes = R.string.pref_library_update_refresh_metadata
|
|
||||||
summaryRes = R.string.pref_library_update_refresh_metadata_summary
|
|
||||||
}
|
|
||||||
if (trackManager.hasLoggedServices()) {
|
|
||||||
switchPreference {
|
|
||||||
bindTo(libraryPreferences.autoUpdateTrackers())
|
|
||||||
titleRes = R.string.pref_library_update_refresh_trackers
|
|
||||||
summaryRes = R.string.pref_library_update_refresh_trackers_summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryColumnsDialog : DialogController() {
|
|
||||||
|
|
||||||
private val preferences: LibraryPreferences = Injekt.get()
|
|
||||||
|
|
||||||
private var portrait = preferences.portraitColumns().get()
|
|
||||||
private var landscape = preferences.landscapeColumns().get()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val binding = PrefLibraryColumnsBinding.inflate(LayoutInflater.from(activity!!))
|
|
||||||
onViewCreated(binding)
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.pref_library_columns)
|
|
||||||
.setView(binding.root)
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
preferences.portraitColumns().set(portrait)
|
|
||||||
preferences.landscapeColumns().set(landscape)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onViewCreated(binding: PrefLibraryColumnsBinding) {
|
|
||||||
with(binding.portraitColumns) {
|
|
||||||
displayedValues = arrayOf(context.getString(R.string.label_default)) +
|
|
||||||
IntRange(1, 10).map(Int::toString)
|
|
||||||
value = portrait
|
|
||||||
|
|
||||||
setOnValueChangedListener { _, _, newValue ->
|
|
||||||
portrait = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
with(binding.landscapeColumns) {
|
|
||||||
displayedValues = arrayOf(context.getString(R.string.label_default)) +
|
|
||||||
IntRange(1, 10).map(Int::toString)
|
|
||||||
value = landscape
|
|
||||||
|
|
||||||
setOnValueChangedListener { _, _, newValue ->
|
|
||||||
landscape = newValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class LibraryGlobalUpdateCategoriesDialog : DialogController() {
|
|
||||||
|
|
||||||
private val preferences: LibraryPreferences = Injekt.get()
|
|
||||||
private val getCategories: GetCategories = Injekt.get()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val categories = runBlocking { getCategories.await() }
|
|
||||||
|
|
||||||
val items = categories.map { it.visualName(activity!!) }
|
|
||||||
var selected = categories
|
|
||||||
.map {
|
|
||||||
when (it.id.toString()) {
|
|
||||||
in preferences.libraryUpdateCategories()
|
|
||||||
.get(),
|
|
||||||
-> QuadStateTextView.State.CHECKED.ordinal
|
|
||||||
in preferences.libraryUpdateCategoriesExclude()
|
|
||||||
.get(),
|
|
||||||
-> QuadStateTextView.State.INVERSED.ordinal
|
|
||||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toIntArray()
|
|
||||||
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(R.string.categories)
|
|
||||||
.setQuadStateMultiChoiceItems(
|
|
||||||
message = R.string.pref_library_update_categories_details,
|
|
||||||
items = items,
|
|
||||||
initialSelected = selected,
|
|
||||||
) { selections ->
|
|
||||||
selected = selections
|
|
||||||
}
|
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
val included = selected
|
|
||||||
.mapIndexed { index, value -> if (value == QuadStateTextView.State.CHECKED.ordinal) index else null }
|
|
||||||
.filterNotNull()
|
|
||||||
.map { categories[it].id.toString() }
|
|
||||||
.toSet()
|
|
||||||
val excluded = selected
|
|
||||||
.mapIndexed { index, value -> if (value == QuadStateTextView.State.INVERSED.ordinal) index else null }
|
|
||||||
.filterNotNull()
|
|
||||||
.map { categories[it].id.toString() }
|
|
||||||
.toSet()
|
|
||||||
|
|
||||||
preferences.libraryUpdateCategories().set(included)
|
|
||||||
preferences.libraryUpdateCategoriesExclude().set(excluded)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,325 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceValues.TappingInvertMode
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
|
||||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.listPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.summaryRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsReaderController : SettingsController() {
|
|
||||||
|
|
||||||
private val readerPreferences: ReaderPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_reader
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.defaultReadingMode())
|
|
||||||
titleRes = R.string.pref_viewer_type
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.left_to_right_viewer,
|
|
||||||
R.string.right_to_left_viewer,
|
|
||||||
R.string.vertical_viewer,
|
|
||||||
R.string.webtoon_viewer,
|
|
||||||
R.string.vertical_plus_viewer,
|
|
||||||
)
|
|
||||||
entryValues = ReadingModeType.values().drop(1)
|
|
||||||
.map { value -> "${value.flagValue}" }.toTypedArray()
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.doubleTapAnimSpeed())
|
|
||||||
titleRes = R.string.pref_double_tap_anim_speed
|
|
||||||
entries = arrayOf(context.getString(R.string.double_tap_anim_speed_0), context.getString(R.string.double_tap_anim_speed_normal), context.getString(R.string.double_tap_anim_speed_fast))
|
|
||||||
entryValues = arrayOf("1", "500", "250") // using a value of 0 breaks the image viewer, so min is 1
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.showReadingMode())
|
|
||||||
titleRes = R.string.pref_show_reading_mode
|
|
||||||
summaryRes = R.string.pref_show_reading_mode_summary
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.showNavigationOverlayOnStart())
|
|
||||||
titleRes = R.string.pref_show_navigation_mode
|
|
||||||
summaryRes = R.string.pref_show_navigation_mode_summary
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.trueColor())
|
|
||||||
titleRes = R.string.pref_true_color
|
|
||||||
summaryRes = R.string.pref_true_color_summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.pageTransitions())
|
|
||||||
titleRes = R.string.pref_page_transitions
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_display
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.defaultOrientationType())
|
|
||||||
titleRes = R.string.pref_rotation_type
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.rotation_free,
|
|
||||||
R.string.rotation_portrait,
|
|
||||||
R.string.rotation_reverse_portrait,
|
|
||||||
R.string.rotation_landscape,
|
|
||||||
R.string.rotation_force_portrait,
|
|
||||||
R.string.rotation_force_landscape,
|
|
||||||
)
|
|
||||||
entryValues = OrientationType.values().drop(1)
|
|
||||||
.map { value -> "${value.flagValue}" }.toTypedArray()
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.readerTheme())
|
|
||||||
titleRes = R.string.pref_reader_theme
|
|
||||||
entriesRes = arrayOf(R.string.black_background, R.string.gray_background, R.string.white_background, R.string.automatic_background)
|
|
||||||
entryValues = arrayOf("1", "2", "0", "3")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.fullscreen())
|
|
||||||
titleRes = R.string.pref_fullscreen
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activity?.hasDisplayCutout() == true) {
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.cutoutShort())
|
|
||||||
titleRes = R.string.pref_cutout_short
|
|
||||||
|
|
||||||
visibleIf(readerPreferences.fullscreen()) { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.keepScreenOn())
|
|
||||||
titleRes = R.string.pref_keep_screen_on
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.showPageNumber())
|
|
||||||
titleRes = R.string.pref_show_page_number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_category_reading
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.skipRead())
|
|
||||||
titleRes = R.string.pref_skip_read_chapters
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.skipFiltered())
|
|
||||||
titleRes = R.string.pref_skip_filtered_chapters
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.alwaysShowChapterTransition())
|
|
||||||
titleRes = R.string.pref_always_show_chapter_transition
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pager_viewer
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.navigationModePager())
|
|
||||||
titleRes = R.string.pref_viewer_nav
|
|
||||||
entries = context.resources.getStringArray(R.array.pager_nav).also { values ->
|
|
||||||
entryValues = values.indices.map { index -> "$index" }.toTypedArray()
|
|
||||||
}
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
listPreference {
|
|
||||||
bindTo(readerPreferences.pagerNavInverted())
|
|
||||||
titleRes = R.string.pref_read_with_tapping_inverted
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.tapping_inverted_none,
|
|
||||||
R.string.tapping_inverted_horizontal,
|
|
||||||
R.string.tapping_inverted_vertical,
|
|
||||||
R.string.tapping_inverted_both,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf(
|
|
||||||
TappingInvertMode.NONE.name,
|
|
||||||
TappingInvertMode.HORIZONTAL.name,
|
|
||||||
TappingInvertMode.VERTICAL.name,
|
|
||||||
TappingInvertMode.BOTH.name,
|
|
||||||
)
|
|
||||||
summary = "%s"
|
|
||||||
visibleIf(readerPreferences.navigationModePager()) { it != 5 }
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.navigateToPan())
|
|
||||||
titleRes = R.string.pref_navigate_pan
|
|
||||||
visibleIf(readerPreferences.navigationModePager()) { it != 5 }
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.imageScaleType())
|
|
||||||
titleRes = R.string.pref_image_scale_type
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.scale_type_fit_screen,
|
|
||||||
R.string.scale_type_stretch,
|
|
||||||
R.string.scale_type_fit_width,
|
|
||||||
R.string.scale_type_fit_height,
|
|
||||||
R.string.scale_type_original_size,
|
|
||||||
R.string.scale_type_smart_fit,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("1", "2", "3", "4", "5", "6")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.landscapeZoom())
|
|
||||||
titleRes = R.string.pref_landscape_zoom
|
|
||||||
visibleIf(readerPreferences.imageScaleType()) { it == 1 }
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.zoomStart())
|
|
||||||
titleRes = R.string.pref_zoom_start
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.zoom_start_automatic,
|
|
||||||
R.string.zoom_start_left,
|
|
||||||
R.string.zoom_start_right,
|
|
||||||
R.string.zoom_start_center,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("1", "2", "3", "4")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.cropBorders())
|
|
||||||
titleRes = R.string.pref_crop_borders
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.dualPageSplitPaged())
|
|
||||||
titleRes = R.string.pref_dual_page_split
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.dualPageInvertPaged())
|
|
||||||
titleRes = R.string.pref_dual_page_invert
|
|
||||||
summaryRes = R.string.pref_dual_page_invert_summary
|
|
||||||
visibleIf(readerPreferences.dualPageSplitPaged()) { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.webtoon_viewer
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.navigationModeWebtoon())
|
|
||||||
titleRes = R.string.pref_viewer_nav
|
|
||||||
entries = context.resources.getStringArray(R.array.webtoon_nav).also { values ->
|
|
||||||
entryValues = values.indices.map { index -> "$index" }.toTypedArray()
|
|
||||||
}
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
listPreference {
|
|
||||||
bindTo(readerPreferences.webtoonNavInverted())
|
|
||||||
titleRes = R.string.pref_read_with_tapping_inverted
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.tapping_inverted_none,
|
|
||||||
R.string.tapping_inverted_horizontal,
|
|
||||||
R.string.tapping_inverted_vertical,
|
|
||||||
R.string.tapping_inverted_both,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf(
|
|
||||||
TappingInvertMode.NONE.name,
|
|
||||||
TappingInvertMode.HORIZONTAL.name,
|
|
||||||
TappingInvertMode.VERTICAL.name,
|
|
||||||
TappingInvertMode.BOTH.name,
|
|
||||||
)
|
|
||||||
summary = "%s"
|
|
||||||
visibleIf(readerPreferences.navigationModeWebtoon()) { it != 5 }
|
|
||||||
}
|
|
||||||
intListPreference {
|
|
||||||
bindTo(readerPreferences.webtoonSidePadding())
|
|
||||||
titleRes = R.string.pref_webtoon_side_padding
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.webtoon_side_padding_0,
|
|
||||||
R.string.webtoon_side_padding_5,
|
|
||||||
R.string.webtoon_side_padding_10,
|
|
||||||
R.string.webtoon_side_padding_15,
|
|
||||||
R.string.webtoon_side_padding_20,
|
|
||||||
R.string.webtoon_side_padding_25,
|
|
||||||
)
|
|
||||||
entryValues = arrayOf("0", "5", "10", "15", "20", "25")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
listPreference {
|
|
||||||
bindTo(readerPreferences.readerHideThreshold())
|
|
||||||
titleRes = R.string.pref_hide_threshold
|
|
||||||
entriesRes = arrayOf(
|
|
||||||
R.string.pref_highest,
|
|
||||||
R.string.pref_high,
|
|
||||||
R.string.pref_low,
|
|
||||||
R.string.pref_lowest,
|
|
||||||
)
|
|
||||||
entryValues = PreferenceValues.ReaderHideThreshold.values()
|
|
||||||
.map { it.name }
|
|
||||||
.toTypedArray()
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.cropBordersWebtoon())
|
|
||||||
titleRes = R.string.pref_crop_borders
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.dualPageSplitWebtoon())
|
|
||||||
titleRes = R.string.pref_dual_page_split
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.dualPageInvertWebtoon())
|
|
||||||
titleRes = R.string.pref_dual_page_invert
|
|
||||||
summaryRes = R.string.pref_dual_page_invert_summary
|
|
||||||
visibleIf(readerPreferences.dualPageSplitWebtoon()) { it }
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.longStripSplitWebtoon())
|
|
||||||
titleRes = R.string.pref_long_strip_split
|
|
||||||
summaryRes = R.string.split_tall_images_summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_reader_navigation
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.readWithVolumeKeys())
|
|
||||||
titleRes = R.string.pref_read_with_volume_keys
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.readWithVolumeKeysInverted())
|
|
||||||
titleRes = R.string.pref_read_with_volume_keys_inverted
|
|
||||||
visibleIf(readerPreferences.readWithVolumeKeys()) { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.pref_reader_actions
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.readWithLongTap())
|
|
||||||
titleRes = R.string.pref_read_with_long_tap
|
|
||||||
}
|
|
||||||
switchPreference {
|
|
||||||
bindTo(readerPreferences.folderPerManga())
|
|
||||||
titleRes = R.string.pref_create_folder_per_manga
|
|
||||||
summaryRes = R.string.pref_create_folder_per_manga_summary
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import androidx.biometric.BiometricPrompt
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.infoPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.listPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.requireAuthentication
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsSecurityController : SettingsController() {
|
|
||||||
|
|
||||||
private val securityPreferences: SecurityPreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_security
|
|
||||||
|
|
||||||
if (context.isAuthenticationSupported()) {
|
|
||||||
switchPreference {
|
|
||||||
bindTo(securityPreferences.useAuthenticator())
|
|
||||||
titleRes = R.string.lock_with_biometrics
|
|
||||||
|
|
||||||
requireAuthentication(
|
|
||||||
activity as? FragmentActivity,
|
|
||||||
context.getString(R.string.lock_with_biometrics),
|
|
||||||
context.getString(R.string.confirm_lock_change),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
intListPreference {
|
|
||||||
bindTo(securityPreferences.lockAppAfter())
|
|
||||||
titleRes = R.string.lock_when_idle
|
|
||||||
val values = arrayOf("0", "1", "2", "5", "10", "-1")
|
|
||||||
entries = values.mapNotNull {
|
|
||||||
when (it) {
|
|
||||||
"-1" -> context.getString(R.string.lock_never)
|
|
||||||
"0" -> context.getString(R.string.lock_always)
|
|
||||||
else -> resources?.getQuantityString(R.plurals.lock_after_mins, it.toInt(), it)
|
|
||||||
}
|
|
||||||
}.toTypedArray()
|
|
||||||
entryValues = values
|
|
||||||
summary = "%s"
|
|
||||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
|
||||||
if (value == newValue) return@OnPreferenceChangeListener false
|
|
||||||
|
|
||||||
(activity as? FragmentActivity)?.startAuthentication(
|
|
||||||
activity!!.getString(R.string.lock_when_idle),
|
|
||||||
activity!!.getString(R.string.confirm_lock_change),
|
|
||||||
callback = object : AuthenticatorUtil.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
activity: FragmentActivity?,
|
|
||||||
result: BiometricPrompt.AuthenticationResult,
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(activity, result)
|
|
||||||
value = newValue as String
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
|
||||||
activity: FragmentActivity?,
|
|
||||||
errorCode: Int,
|
|
||||||
errString: CharSequence,
|
|
||||||
) {
|
|
||||||
super.onAuthenticationError(activity, errorCode, errString)
|
|
||||||
activity?.toast(errString.toString())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
visibleIf(securityPreferences.useAuthenticator()) { it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(securityPreferences.hideNotificationContent())
|
|
||||||
titleRes = R.string.hide_notification_content
|
|
||||||
}
|
|
||||||
|
|
||||||
listPreference {
|
|
||||||
bindTo(securityPreferences.secureScreen())
|
|
||||||
titleRes = R.string.secure_screen
|
|
||||||
summary = "%s"
|
|
||||||
entriesRes = SecurityPreferences.SecureScreenMode.values().map { it.titleResId }.toTypedArray()
|
|
||||||
entryValues = SecurityPreferences.SecureScreenMode.values().map { it.name }.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
infoPreference(R.string.secure_screen_summary)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.view.Menu
|
|
||||||
import android.view.MenuInflater
|
|
||||||
import android.view.MenuItem
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.preference.PreferenceGroup
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.domain.track.service.TrackPreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.track.NoLoginTrackService
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
import eu.kanade.tachiyomi.data.track.anilist.AnilistApi
|
|
||||||
import eu.kanade.tachiyomi.data.track.bangumi.BangumiApi
|
|
||||||
import eu.kanade.tachiyomi.data.track.myanimelist.MyAnimeListApi
|
|
||||||
import eu.kanade.tachiyomi.data.track.shikimori.ShikimoriApi
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.track.TrackLoginDialog
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.track.TrackLogoutDialog
|
|
||||||
import eu.kanade.tachiyomi.util.preference.add
|
|
||||||
import eu.kanade.tachiyomi.util.preference.bindTo
|
|
||||||
import eu.kanade.tachiyomi.util.preference.iconRes
|
|
||||||
import eu.kanade.tachiyomi.util.preference.infoPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.onClick
|
|
||||||
import eu.kanade.tachiyomi.util.preference.preferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.util.preference.switchPreference
|
|
||||||
import eu.kanade.tachiyomi.util.preference.titleRes
|
|
||||||
import eu.kanade.tachiyomi.util.system.openInBrowser
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.TrackerPreference
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class SettingsTrackingController :
|
|
||||||
SettingsController(),
|
|
||||||
TrackLoginDialog.Listener,
|
|
||||||
TrackLogoutDialog.Listener {
|
|
||||||
|
|
||||||
private val trackManager: TrackManager by injectLazy()
|
|
||||||
private val trackPreferences: TrackPreferences by injectLazy()
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
|
||||||
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
|
|
||||||
titleRes = R.string.pref_category_tracking
|
|
||||||
|
|
||||||
switchPreference {
|
|
||||||
bindTo(trackPreferences.autoUpdateTrack())
|
|
||||||
titleRes = R.string.pref_auto_update_manga_sync
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.services
|
|
||||||
|
|
||||||
trackPreference(trackManager.myAnimeList) {
|
|
||||||
activity?.openInBrowser(MyAnimeListApi.authUrl(), forceDefaultBrowser = true)
|
|
||||||
}
|
|
||||||
trackPreference(trackManager.aniList) {
|
|
||||||
activity?.openInBrowser(AnilistApi.authUrl(), forceDefaultBrowser = true)
|
|
||||||
}
|
|
||||||
trackPreference(trackManager.kitsu) {
|
|
||||||
val dialog = TrackLoginDialog(trackManager.kitsu, R.string.email)
|
|
||||||
dialog.targetController = this@SettingsTrackingController
|
|
||||||
dialog.showDialog(router)
|
|
||||||
}
|
|
||||||
trackPreference(trackManager.mangaUpdates) {
|
|
||||||
val dialog = TrackLoginDialog(trackManager.mangaUpdates, R.string.username)
|
|
||||||
dialog.targetController = this@SettingsTrackingController
|
|
||||||
dialog.showDialog(router)
|
|
||||||
}
|
|
||||||
trackPreference(trackManager.shikimori) {
|
|
||||||
activity?.openInBrowser(ShikimoriApi.authUrl(), forceDefaultBrowser = true)
|
|
||||||
}
|
|
||||||
trackPreference(trackManager.bangumi) {
|
|
||||||
activity?.openInBrowser(BangumiApi.authUrl(), forceDefaultBrowser = true)
|
|
||||||
}
|
|
||||||
infoPreference(R.string.tracking_info)
|
|
||||||
}
|
|
||||||
|
|
||||||
preferenceCategory {
|
|
||||||
titleRes = R.string.enhanced_services
|
|
||||||
|
|
||||||
trackPreference(trackManager.komga) {
|
|
||||||
val acceptedSources = trackManager.komga.getAcceptedSources()
|
|
||||||
val hasValidSourceInstalled = sourceManager.getCatalogueSources()
|
|
||||||
.any { it::class.qualifiedName in acceptedSources }
|
|
||||||
|
|
||||||
if (hasValidSourceInstalled) {
|
|
||||||
trackManager.komga.loginNoop()
|
|
||||||
updatePreference(trackManager.komga.id)
|
|
||||||
} else {
|
|
||||||
context.toast(R.string.tracker_komga_warning, Toast.LENGTH_LONG)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
infoPreference(R.string.enhanced_tracking_info)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun PreferenceGroup.trackPreference(
|
|
||||||
service: TrackService,
|
|
||||||
crossinline login: () -> Unit,
|
|
||||||
): TrackerPreference {
|
|
||||||
return add(
|
|
||||||
TrackerPreference(context).apply {
|
|
||||||
key = TrackPreferences.trackUsername(service.id)
|
|
||||||
titleRes = service.nameRes()
|
|
||||||
iconRes = service.getLogo()
|
|
||||||
iconColor = service.getLogoColor()
|
|
||||||
onClick {
|
|
||||||
if (service.isLogged) {
|
|
||||||
if (service is NoLoginTrackService) {
|
|
||||||
service.logout()
|
|
||||||
updatePreference(service.id)
|
|
||||||
} else {
|
|
||||||
val dialog = TrackLogoutDialog(service)
|
|
||||||
dialog.targetController = this@SettingsTrackingController
|
|
||||||
dialog.showDialog(router)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
login()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityResumed(activity: Activity) {
|
|
||||||
super.onActivityResumed(activity)
|
|
||||||
|
|
||||||
// Manually refresh OAuth trackers' holders
|
|
||||||
updatePreference(trackManager.myAnimeList.id)
|
|
||||||
updatePreference(trackManager.aniList.id)
|
|
||||||
updatePreference(trackManager.shikimori.id)
|
|
||||||
updatePreference(trackManager.bangumi.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
||||||
inflater.inflate(R.menu.settings_tracking, menu)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
when (item.itemId) {
|
|
||||||
R.id.action_tracking_help -> activity?.openInBrowser(HELP_URL)
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePreference(id: Long) {
|
|
||||||
val pref = findPreference(TrackPreferences.trackUsername(id)) as? TrackerPreference
|
|
||||||
pref?.notifyChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun trackLoginDialogClosed(service: TrackService) {
|
|
||||||
updatePreference(service.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun trackLogoutDialogClosed(service: TrackService) {
|
|
||||||
updatePreference(service.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val HELP_URL = "https://tachiyomi.org/help/guides/tracking/"
|
|
|
@ -1,20 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.database
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import eu.kanade.presentation.more.settings.database.ClearDatabaseScreen
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
|
||||||
|
|
||||||
class ClearDatabaseController : FullComposeController<ClearDatabasePresenter>() {
|
|
||||||
|
|
||||||
override fun createPresenter(): ClearDatabasePresenter {
|
|
||||||
return ClearDatabasePresenter()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
ClearDatabaseScreen(
|
|
||||||
presenter = presenter,
|
|
||||||
navigateUp = { router.popCurrentController() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.database
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import eu.kanade.domain.source.interactor.GetSourcesWithNonLibraryManga
|
|
||||||
import eu.kanade.domain.source.model.Source
|
|
||||||
import eu.kanade.presentation.more.settings.database.ClearDatabaseState
|
|
||||||
import eu.kanade.presentation.more.settings.database.ClearDatabaseStateImpl
|
|
||||||
import eu.kanade.tachiyomi.Database
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import kotlinx.coroutines.flow.collectLatest
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class ClearDatabasePresenter(
|
|
||||||
private val state: ClearDatabaseStateImpl = ClearDatabaseState() as ClearDatabaseStateImpl,
|
|
||||||
private val database: Database = Injekt.get(),
|
|
||||||
private val getSourcesWithNonLibraryManga: GetSourcesWithNonLibraryManga = Injekt.get(),
|
|
||||||
) : BasePresenter<ClearDatabaseController>(), ClearDatabaseState by state {
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
presenterScope.launchIO {
|
|
||||||
getSourcesWithNonLibraryManga.subscribe()
|
|
||||||
.collectLatest { list ->
|
|
||||||
state.items = list.sortedBy { it.name }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeMangaBySourceId(sourceIds: List<Long>) {
|
|
||||||
database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sourceIds)
|
|
||||||
database.historyQueries.removeResettedHistory()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleSelection(source: Source) {
|
|
||||||
val mutableList = state.selection.toMutableList()
|
|
||||||
if (mutableList.contains(source.id)) {
|
|
||||||
mutableList.remove(source.id)
|
|
||||||
} else {
|
|
||||||
mutableList.add(source.id)
|
|
||||||
}
|
|
||||||
state.selection = mutableList
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearSelection() {
|
|
||||||
state.selection = emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun selectAll() {
|
|
||||||
state.selection = state.items.map { it.id }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invertSelection() {
|
|
||||||
state.selection = state.items.map { it.id }.filterNot { it in state.selection }
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed class Dialog {
|
|
||||||
data class Delete(val sourceIds: List<Long>) : Dialog()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.search
|
|
||||||
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import eu.kanade.presentation.more.settings.SettingsSearchScreen
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.FullComposeController
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.pushController
|
|
||||||
|
|
||||||
class SettingsSearchController : FullComposeController<SettingsSearchPresenter>() {
|
|
||||||
|
|
||||||
override fun createPresenter() = SettingsSearchPresenter()
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
override fun ComposeContent() {
|
|
||||||
SettingsSearchScreen(
|
|
||||||
navigateUp = router::popCurrentController,
|
|
||||||
presenter = presenter,
|
|
||||||
onClickResult = { router.pushController(it) },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,138 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.search
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.Resources
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceCategory
|
|
||||||
import androidx.preference.PreferenceGroup
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.preference.forEach
|
|
||||||
import androidx.preference.get
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsAdvancedController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsAppearanceController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsBackupController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsBrowseController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsDownloadController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsGeneralController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsLibraryController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsReaderController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsSecurityController
|
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsTrackingController
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchNow
|
|
||||||
import eu.kanade.tachiyomi.util.system.isLTR
|
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.full.createInstance
|
|
||||||
|
|
||||||
object SettingsSearchHelper {
|
|
||||||
private var prefSearchResultList: MutableList<SettingsSearchResult> = mutableListOf()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* All subclasses of `SettingsController` should be listed here, in order to have their preferences searchable.
|
|
||||||
*/
|
|
||||||
private val settingControllersList: List<KClass<out SettingsController>> = listOf(
|
|
||||||
SettingsAdvancedController::class,
|
|
||||||
SettingsAppearanceController::class,
|
|
||||||
SettingsBackupController::class,
|
|
||||||
SettingsBrowseController::class,
|
|
||||||
SettingsDownloadController::class,
|
|
||||||
SettingsGeneralController::class,
|
|
||||||
SettingsLibraryController::class,
|
|
||||||
SettingsReaderController::class,
|
|
||||||
SettingsSecurityController::class,
|
|
||||||
SettingsTrackingController::class,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Must be called to populate `prefSearchResultList`
|
|
||||||
*/
|
|
||||||
@SuppressLint("RestrictedApi")
|
|
||||||
fun initPreferenceSearchResults(context: Context) {
|
|
||||||
val preferenceManager = PreferenceManager(context)
|
|
||||||
prefSearchResultList.clear()
|
|
||||||
|
|
||||||
launchNow {
|
|
||||||
settingControllersList.forEach { kClass ->
|
|
||||||
val ctrl = kClass.createInstance()
|
|
||||||
val settingsPrefScreen = ctrl.setupPreferenceScreen(preferenceManager.createPreferenceScreen(context))
|
|
||||||
val prefCount = settingsPrefScreen.preferenceCount
|
|
||||||
for (i in 0 until prefCount) {
|
|
||||||
val rootPref = settingsPrefScreen[i]
|
|
||||||
if (rootPref.title == null) continue // no title, not a preference. (note: only info notes appear to not have titles)
|
|
||||||
getSettingSearchResult(ctrl, rootPref, "${settingsPrefScreen.title}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getFilteredResults(query: String): List<SettingsSearchResult> {
|
|
||||||
return prefSearchResultList.filter {
|
|
||||||
val inTitle = it.title.contains(query, true)
|
|
||||||
val inSummary = it.summary.contains(query, true)
|
|
||||||
val inBreadcrumb = it.breadcrumb.contains(query, true)
|
|
||||||
|
|
||||||
return@filter inTitle || inSummary || inBreadcrumb
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extracts the data needed from a `Preference` to create a `SettingsSearchResult`, and then adds it to `prefSearchResultList`
|
|
||||||
* Future enhancement: make bold the text matched by the search query.
|
|
||||||
*/
|
|
||||||
private fun getSettingSearchResult(
|
|
||||||
ctrl: SettingsController,
|
|
||||||
pref: Preference,
|
|
||||||
breadcrumbs: String = "",
|
|
||||||
) {
|
|
||||||
when {
|
|
||||||
pref is PreferenceGroup -> {
|
|
||||||
val breadcrumbsStr = addLocalizedBreadcrumb(breadcrumbs, "${pref.title}")
|
|
||||||
pref.forEach {
|
|
||||||
getSettingSearchResult(ctrl, it, breadcrumbsStr) // recursion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pref is PreferenceCategory -> {
|
|
||||||
val breadcrumbsStr = addLocalizedBreadcrumb(breadcrumbs, "${pref.title}")
|
|
||||||
pref.forEach {
|
|
||||||
getSettingSearchResult(ctrl, it, breadcrumbsStr) // recursion
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(pref.title != null && pref.isVisible) -> {
|
|
||||||
// Is an actual preference
|
|
||||||
val title = pref.title.toString()
|
|
||||||
// ListPreferences occasionally run into ArrayIndexOutOfBoundsException issues
|
|
||||||
val summary = try { pref.summary?.toString() ?: "" } catch (e: Throwable) { "" }
|
|
||||||
val breadcrumbsStr = addLocalizedBreadcrumb(breadcrumbs, "${pref.title}")
|
|
||||||
|
|
||||||
prefSearchResultList.add(
|
|
||||||
SettingsSearchResult(
|
|
||||||
key = pref.key,
|
|
||||||
title = title,
|
|
||||||
summary = summary,
|
|
||||||
breadcrumb = breadcrumbsStr,
|
|
||||||
searchController = ctrl,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addLocalizedBreadcrumb(path: String, node: String): String {
|
|
||||||
return if (Resources.getSystem().isLTR) {
|
|
||||||
// This locale reads left to right.
|
|
||||||
"$path > $node"
|
|
||||||
} else {
|
|
||||||
// This locale reads right to left.
|
|
||||||
"$node < $path"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class SettingsSearchResult(
|
|
||||||
val key: String?,
|
|
||||||
val title: String,
|
|
||||||
val summary: String,
|
|
||||||
val breadcrumb: String,
|
|
||||||
val searchController: SettingsController,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.search
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class SettingsSearchPresenter(
|
|
||||||
private val preferences: BasePreferences = Injekt.get(),
|
|
||||||
) : BasePresenter<SettingsSearchController>() {
|
|
||||||
|
|
||||||
private val _state: MutableStateFlow<List<SettingsSearchHelper.SettingsSearchResult>> =
|
|
||||||
MutableStateFlow(emptyList())
|
|
||||||
val state: StateFlow<List<SettingsSearchHelper.SettingsSearchResult>> = _state.asStateFlow()
|
|
||||||
|
|
||||||
override fun onCreate(savedState: Bundle?) {
|
|
||||||
super.onCreate(savedState)
|
|
||||||
|
|
||||||
SettingsSearchHelper.initPreferenceSearchResults(preferences.context)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun searchSettings(query: String?) {
|
|
||||||
_state.value = if (!query.isNullOrBlank()) {
|
|
||||||
SettingsSearchHelper.getFilteredResults(query)
|
|
||||||
} else {
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.track
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
|
||||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.LoginDialogPreference
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class TrackLoginDialog(
|
|
||||||
@StringRes usernameLabelRes: Int? = null,
|
|
||||||
bundle: Bundle? = null,
|
|
||||||
) : LoginDialogPreference(usernameLabelRes, bundle) {
|
|
||||||
|
|
||||||
private val service = Injekt.get<TrackManager>().getService(args.getLong("serviceId"))!!
|
|
||||||
|
|
||||||
constructor(service: TrackService, @StringRes usernameLabelRes: Int?) :
|
|
||||||
this(usernameLabelRes, bundleOf("serviceId" to service.id))
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
override fun getTitleName(): Int = service.nameRes()
|
|
||||||
|
|
||||||
override fun setCredentialsOnView(view: View) {
|
|
||||||
binding?.username?.setText(service.getUsername())
|
|
||||||
binding?.password?.setText(service.getPassword())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun checkLogin() {
|
|
||||||
if (binding!!.username.text.isNullOrEmpty() || binding!!.password.text.isNullOrEmpty()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
binding!!.login.progress = 1
|
|
||||||
val user = binding!!.username.text.toString()
|
|
||||||
val pass = binding!!.password.text.toString()
|
|
||||||
|
|
||||||
launchIO {
|
|
||||||
try {
|
|
||||||
service.login(user, pass)
|
|
||||||
dialog?.dismiss()
|
|
||||||
withUIContext { view?.context?.toast(R.string.login_success) }
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
service.logout()
|
|
||||||
binding?.login?.progress = -1
|
|
||||||
binding?.login?.setText(R.string.unknown_error)
|
|
||||||
withUIContext { e.message?.let { view?.context?.toast(it) } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDialogClosed() {
|
|
||||||
super.onDialogClosed()
|
|
||||||
(targetController as? Listener)?.trackLoginDialogClosed(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun trackLoginDialogClosed(service: TrackService)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.ui.setting.track
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackService
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class TrackLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
|
|
||||||
|
|
||||||
private val service = Injekt.get<TrackManager>().getService(args.getLong("serviceId"))!!
|
|
||||||
|
|
||||||
constructor(service: TrackService) : this(bundleOf("serviceId" to service.id))
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
val serviceName = activity!!.getString(service.nameRes())
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(activity!!.getString(R.string.logout_title, serviceName))
|
|
||||||
.setPositiveButton(R.string.logout) { _, _ ->
|
|
||||||
service.logout()
|
|
||||||
(targetController as? Listener)?.trackLogoutDialogClosed(service)
|
|
||||||
activity?.toast(R.string.logout_success)
|
|
||||||
}
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Listener {
|
|
||||||
fun trackLogoutDialogClosed(service: TrackService)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,198 +0,0 @@
|
||||||
@file:Suppress("NOTHING_TO_INLINE")
|
|
||||||
|
|
||||||
package eu.kanade.tachiyomi.util.preference
|
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.biometric.BiometricPrompt
|
|
||||||
import androidx.fragment.app.FragmentActivity
|
|
||||||
import androidx.preference.CheckBoxPreference
|
|
||||||
import androidx.preference.EditTextPreference
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.MultiSelectListPreference
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceCategory
|
|
||||||
import androidx.preference.PreferenceGroup
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.isAuthenticationSupported
|
|
||||||
import eu.kanade.tachiyomi.util.system.AuthenticatorUtil.startAuthentication
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.AdaptiveTitlePreferenceCategory
|
|
||||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
|
||||||
|
|
||||||
@DslMarker
|
|
||||||
@Target(AnnotationTarget.TYPE)
|
|
||||||
annotation class DSL
|
|
||||||
|
|
||||||
inline fun PreferenceManager.newScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
|
|
||||||
return createPreferenceScreen(context).also { it.block() }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.preference(block: (@DSL Preference).() -> Unit): Preference {
|
|
||||||
return initThenAdd(Preference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.infoPreference(@StringRes infoRes: Int): Preference {
|
|
||||||
return add(
|
|
||||||
Preference(context).apply {
|
|
||||||
iconRes = R.drawable.ic_info_24dp
|
|
||||||
iconTint = context.getResourceColor(android.R.attr.textColorHint)
|
|
||||||
summaryRes = infoRes
|
|
||||||
isSelectable = false
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.switchPreference(block: (@DSL SwitchPreferenceCompat).() -> Unit): SwitchPreferenceCompat {
|
|
||||||
return initThenAdd(SwitchPreferenceCompat(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.checkBoxPreference(block: (@DSL CheckBoxPreference).() -> Unit): CheckBoxPreference {
|
|
||||||
return initThenAdd(CheckBoxPreference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.editTextPreference(block: (@DSL EditTextPreference).() -> Unit): EditTextPreference {
|
|
||||||
return initThenAdd(EditTextPreference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.listPreference(block: (@DSL ListPreference).() -> Unit): ListPreference {
|
|
||||||
return initThenAdd(ListPreference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.intListPreference(block: (@DSL IntListPreference).() -> Unit): IntListPreference {
|
|
||||||
return initThenAdd(IntListPreference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceGroup.multiSelectListPreference(block: (@DSL MultiSelectListPreference).() -> Unit): MultiSelectListPreference {
|
|
||||||
return initThenAdd(MultiSelectListPreference(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceScreen.preferenceCategory(block: (@DSL PreferenceCategory).() -> Unit): PreferenceCategory {
|
|
||||||
return addThenInit(AdaptiveTitlePreferenceCategory(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun PreferenceScreen.preferenceScreen(block: (@DSL PreferenceScreen).() -> Unit): PreferenceScreen {
|
|
||||||
return addThenInit(preferenceManager.createPreferenceScreen(context), block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <P : Preference> PreferenceGroup.add(p: P): P {
|
|
||||||
return p.apply {
|
|
||||||
this.isIconSpaceReserved = false
|
|
||||||
this.isSingleLineTitle = false
|
|
||||||
addPreference(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <P : Preference> PreferenceGroup.initThenAdd(p: P, block: P.() -> Unit): P {
|
|
||||||
return p.apply {
|
|
||||||
block()
|
|
||||||
this.isIconSpaceReserved = false
|
|
||||||
this.isSingleLineTitle = false
|
|
||||||
addPreference(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <P : Preference> PreferenceGroup.addThenInit(p: P, block: P.() -> Unit): P {
|
|
||||||
return p.apply {
|
|
||||||
this.isIconSpaceReserved = false
|
|
||||||
this.isSingleLineTitle = false
|
|
||||||
addPreference(this)
|
|
||||||
block()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Preference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
|
|
||||||
key = preference.key()
|
|
||||||
defaultValue = preference.defaultValue()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> ListPreference.bindTo(preference: eu.kanade.tachiyomi.core.preference.Preference<T>) {
|
|
||||||
key = preference.key()
|
|
||||||
defaultValue = preference.defaultValue().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun Preference.onClick(crossinline block: () -> Unit) {
|
|
||||||
setOnPreferenceClickListener { block(); true }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun Preference.onChange(crossinline block: (Any?) -> Boolean) {
|
|
||||||
setOnPreferenceChangeListener { _, newValue -> block(newValue) }
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun SwitchPreferenceCompat.requireAuthentication(activity: FragmentActivity?, title: String, subtitle: String?) {
|
|
||||||
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
|
|
||||||
if (context.isAuthenticationSupported()) {
|
|
||||||
activity?.startAuthentication(
|
|
||||||
title,
|
|
||||||
subtitle,
|
|
||||||
callback = object : AuthenticatorUtil.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
activity: FragmentActivity?,
|
|
||||||
result: BiometricPrompt.AuthenticationResult,
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(activity, result)
|
|
||||||
isChecked = newValue as Boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAuthenticationError(
|
|
||||||
activity: FragmentActivity?,
|
|
||||||
errorCode: Int,
|
|
||||||
errString: CharSequence,
|
|
||||||
) {
|
|
||||||
super.onAuthenticationError(activity, errorCode, errString)
|
|
||||||
activity?.toast(errString.toString())
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var Preference.defaultValue: Any?
|
|
||||||
get() = null // set only
|
|
||||||
set(value) {
|
|
||||||
setDefaultValue(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Preference.titleRes: Int
|
|
||||||
get() = 0 // set only
|
|
||||||
set(value) {
|
|
||||||
setTitle(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Preference.iconRes: Int
|
|
||||||
get() = 0 // set only
|
|
||||||
set(value) {
|
|
||||||
icon = AppCompatResources.getDrawable(context, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Preference.summaryRes: Int
|
|
||||||
get() = 0 // set only
|
|
||||||
set(value) {
|
|
||||||
setSummary(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var Preference.iconTint: Int
|
|
||||||
get() = 0 // set only
|
|
||||||
set(value) {
|
|
||||||
icon?.setTint(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var ListPreference.entriesRes: Array<Int>
|
|
||||||
get() = emptyArray() // set only
|
|
||||||
set(value) {
|
|
||||||
entries = value.map { context.getString(it) }.toTypedArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
var MultiSelectListPreference.entriesRes: Array<Int>
|
|
||||||
get() = emptyArray() // set only
|
|
||||||
set(value) {
|
|
||||||
entries = value.map { context.getString(it) }.toTypedArray()
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.tachiyomi.databinding.DialogStubQuadstatemultichoiceBinding
|
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
|
||||||
import kotlin.coroutines.resume
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a list of items with checkboxes that supports 4 states.
|
|
||||||
*
|
|
||||||
* @see eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView
|
|
||||||
*/
|
|
||||||
fun MaterialAlertDialogBuilder.setQuadStateMultiChoiceItems(
|
|
||||||
@StringRes message: Int? = null,
|
|
||||||
isActionList: Boolean = true,
|
|
||||||
items: List<CharSequence>,
|
|
||||||
initialSelected: IntArray,
|
|
||||||
disabledIndices: IntArray? = null,
|
|
||||||
selection: QuadStateMultiChoiceListener,
|
|
||||||
): MaterialAlertDialogBuilder {
|
|
||||||
val binding = DialogStubQuadstatemultichoiceBinding.inflate(LayoutInflater.from(context))
|
|
||||||
binding.list.layoutManager = LinearLayoutManager(context)
|
|
||||||
binding.list.adapter = QuadStateMultiChoiceDialogAdapter(
|
|
||||||
items = items,
|
|
||||||
disabledItems = disabledIndices,
|
|
||||||
initialSelected = initialSelected,
|
|
||||||
isActionList = isActionList,
|
|
||||||
listener = selection,
|
|
||||||
)
|
|
||||||
val updateScrollIndicators = {
|
|
||||||
binding.scrollIndicatorUp.isVisible = binding.list.canScrollVertically(-1)
|
|
||||||
binding.scrollIndicatorDown.isVisible = binding.list.canScrollVertically(1)
|
|
||||||
}
|
|
||||||
binding.list.setOnScrollChangeListener { _, _, _, _, _ ->
|
|
||||||
updateScrollIndicators()
|
|
||||||
}
|
|
||||||
binding.list.post {
|
|
||||||
updateScrollIndicators()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message != null) {
|
|
||||||
binding.message.setText(message)
|
|
||||||
binding.message.isVisible = true
|
|
||||||
}
|
|
||||||
return setView(binding.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun MaterialAlertDialogBuilder.await(
|
|
||||||
@StringRes positiveLabelId: Int,
|
|
||||||
@StringRes negativeLabelId: Int,
|
|
||||||
@StringRes neutralLabelId: Int? = null,
|
|
||||||
) = suspendCancellableCoroutine { cont ->
|
|
||||||
setPositiveButton(positiveLabelId) { _, _ -> cont.resume(AlertDialog.BUTTON_POSITIVE) }
|
|
||||||
setNegativeButton(negativeLabelId) { _, _ -> cont.resume(AlertDialog.BUTTON_NEGATIVE) }
|
|
||||||
if (neutralLabelId != null) {
|
|
||||||
setNeutralButton(neutralLabelId) { _, _ -> cont.resume(AlertDialog.BUTTON_NEUTRAL) }
|
|
||||||
}
|
|
||||||
setOnDismissListener { cont.cancel() }
|
|
||||||
|
|
||||||
val dialog = show()
|
|
||||||
cont.invokeOnCancellation { dialog.dismiss() }
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
|
|
||||||
|
|
||||||
private object CheckPayload
|
|
||||||
private object InverseCheckPayload
|
|
||||||
private object UncheckPayload
|
|
||||||
private object IndeterminatePayload
|
|
||||||
|
|
||||||
typealias QuadStateMultiChoiceListener = (indices: IntArray) -> Unit
|
|
||||||
|
|
||||||
// isAction state: Uncheck-> Check-> Invert else Uncheck-> Indeterminate (only if initial so)-> Check
|
|
||||||
// isAction for list of action to operate on like filter include, exclude
|
|
||||||
internal class QuadStateMultiChoiceDialogAdapter(
|
|
||||||
internal var items: List<CharSequence>,
|
|
||||||
disabledItems: IntArray?,
|
|
||||||
private var initialSelected: IntArray,
|
|
||||||
internal var listener: QuadStateMultiChoiceListener,
|
|
||||||
val isActionList: Boolean = true,
|
|
||||||
) : RecyclerView.Adapter<QuadStateMultiChoiceViewHolder>() {
|
|
||||||
|
|
||||||
private val states = QuadStateTextView.State.values()
|
|
||||||
|
|
||||||
private var currentSelection: IntArray = initialSelected
|
|
||||||
set(value) {
|
|
||||||
val previousSelection = field
|
|
||||||
field = value
|
|
||||||
previousSelection.forEachIndexed { index, previous ->
|
|
||||||
val current = value[index]
|
|
||||||
when {
|
|
||||||
current == QuadStateTextView.State.CHECKED.ordinal && previous != QuadStateTextView.State.CHECKED.ordinal -> {
|
|
||||||
// This value was selected
|
|
||||||
notifyItemChanged(index, CheckPayload)
|
|
||||||
}
|
|
||||||
current == QuadStateTextView.State.INVERSED.ordinal && previous != QuadStateTextView.State.INVERSED.ordinal -> {
|
|
||||||
// This value was inverse selected
|
|
||||||
notifyItemChanged(index, InverseCheckPayload)
|
|
||||||
}
|
|
||||||
current == QuadStateTextView.State.UNCHECKED.ordinal && previous != QuadStateTextView.State.UNCHECKED.ordinal -> {
|
|
||||||
// This value was unselected
|
|
||||||
notifyItemChanged(index, UncheckPayload)
|
|
||||||
}
|
|
||||||
current == QuadStateTextView.State.INDETERMINATE.ordinal && previous != QuadStateTextView.State.INDETERMINATE.ordinal -> {
|
|
||||||
// This value was set back to Indeterminate
|
|
||||||
notifyItemChanged(index, IndeterminatePayload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var disabledIndices: IntArray = disabledItems ?: IntArray(0)
|
|
||||||
internal fun itemActionClicked(index: Int) {
|
|
||||||
val newSelection = this.currentSelection.toMutableList()
|
|
||||||
newSelection[index] = when (currentSelection[index]) {
|
|
||||||
QuadStateTextView.State.CHECKED.ordinal -> QuadStateTextView.State.INVERSED.ordinal
|
|
||||||
QuadStateTextView.State.INVERSED.ordinal -> QuadStateTextView.State.UNCHECKED.ordinal
|
|
||||||
// INDETERMINATE or UNCHECKED
|
|
||||||
else -> QuadStateTextView.State.CHECKED.ordinal
|
|
||||||
}
|
|
||||||
this.currentSelection = newSelection.toIntArray()
|
|
||||||
listener(currentSelection)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun itemDisplayClicked(index: Int) {
|
|
||||||
val newSelection = this.currentSelection.toMutableList()
|
|
||||||
newSelection[index] = when (currentSelection[index]) {
|
|
||||||
QuadStateTextView.State.UNCHECKED.ordinal -> QuadStateTextView.State.CHECKED.ordinal
|
|
||||||
QuadStateTextView.State.CHECKED.ordinal -> when (initialSelected[index]) {
|
|
||||||
QuadStateTextView.State.INDETERMINATE.ordinal -> QuadStateTextView.State.INDETERMINATE.ordinal
|
|
||||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
|
||||||
}
|
|
||||||
// INDETERMINATE or UNCHECKED
|
|
||||||
else -> QuadStateTextView.State.UNCHECKED.ordinal
|
|
||||||
}
|
|
||||||
this.currentSelection = newSelection.toIntArray()
|
|
||||||
listener(currentSelection)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(
|
|
||||||
parent: ViewGroup,
|
|
||||||
viewType: Int,
|
|
||||||
): QuadStateMultiChoiceViewHolder {
|
|
||||||
return QuadStateMultiChoiceViewHolder(
|
|
||||||
itemBinding = DialogQuadstatemultichoiceItemBinding
|
|
||||||
.inflate(LayoutInflater.from(parent.context), parent, false),
|
|
||||||
adapter = this,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = items.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
|
||||||
holder: QuadStateMultiChoiceViewHolder,
|
|
||||||
position: Int,
|
|
||||||
) {
|
|
||||||
holder.isEnabled = !disabledIndices.contains(position)
|
|
||||||
holder.controlView.state = states[currentSelection[position]]
|
|
||||||
holder.controlView.text = items[position]
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(
|
|
||||||
holder: QuadStateMultiChoiceViewHolder,
|
|
||||||
position: Int,
|
|
||||||
payloads: MutableList<Any>,
|
|
||||||
) {
|
|
||||||
when (payloads.firstOrNull()) {
|
|
||||||
CheckPayload -> {
|
|
||||||
holder.controlView.state = QuadStateTextView.State.CHECKED
|
|
||||||
return
|
|
||||||
}
|
|
||||||
InverseCheckPayload -> {
|
|
||||||
holder.controlView.state = QuadStateTextView.State.INVERSED
|
|
||||||
return
|
|
||||||
}
|
|
||||||
UncheckPayload -> {
|
|
||||||
holder.controlView.state = QuadStateTextView.State.UNCHECKED
|
|
||||||
return
|
|
||||||
}
|
|
||||||
IndeterminatePayload -> {
|
|
||||||
holder.controlView.state = QuadStateTextView.State.INDETERMINATE
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.onBindViewHolder(holder, position, payloads)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.kanade.tachiyomi.databinding.DialogQuadstatemultichoiceItemBinding
|
|
||||||
|
|
||||||
internal class QuadStateMultiChoiceViewHolder(
|
|
||||||
itemBinding: DialogQuadstatemultichoiceItemBinding,
|
|
||||||
private val adapter: QuadStateMultiChoiceDialogAdapter,
|
|
||||||
) : RecyclerView.ViewHolder(itemBinding.root), View.OnClickListener {
|
|
||||||
init {
|
|
||||||
itemView.setOnClickListener(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val controlView = itemBinding.quadStateControl
|
|
||||||
|
|
||||||
var isEnabled: Boolean
|
|
||||||
get() = itemView.isEnabled
|
|
||||||
set(value) {
|
|
||||||
itemView.isEnabled = value
|
|
||||||
controlView.isEnabled = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick(view: View) = when (adapter.isActionList) {
|
|
||||||
true -> adapter.itemActionClicked(bindingAdapterPosition)
|
|
||||||
false -> adapter.itemDisplayClicked(bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.materialdialogs
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
|
||||||
import androidx.core.widget.TextViewCompat
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.getThemeColor
|
|
||||||
|
|
||||||
class QuadStateTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
|
||||||
AppCompatTextView(context, attrs) {
|
|
||||||
|
|
||||||
var state: State = State.UNCHECKED
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
updateDrawable()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateDrawable() {
|
|
||||||
val drawableStartId = when (state) {
|
|
||||||
State.UNCHECKED -> R.drawable.ic_check_box_outline_blank_24dp
|
|
||||||
State.INDETERMINATE -> R.drawable.ic_indeterminate_check_box_24dp
|
|
||||||
State.CHECKED -> R.drawable.ic_check_box_24dp
|
|
||||||
State.INVERSED -> R.drawable.ic_check_box_x_24dp
|
|
||||||
}
|
|
||||||
setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStartId, 0, 0, 0)
|
|
||||||
|
|
||||||
val tint = if (state == State.UNCHECKED) {
|
|
||||||
context.getThemeColor(R.attr.colorControlNormal)
|
|
||||||
} else {
|
|
||||||
context.getThemeColor(R.attr.colorPrimary)
|
|
||||||
}
|
|
||||||
if (tint != 0) {
|
|
||||||
TextViewCompat.setCompoundDrawableTintList(this, ColorStateList.valueOf(tint))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class State {
|
|
||||||
UNCHECKED,
|
|
||||||
INDETERMINATE,
|
|
||||||
CHECKED,
|
|
||||||
INVERSED,
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.preference.PreferenceCategory
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PreferenceCategory that hides the title placeholder layout if the title is unset
|
|
||||||
*/
|
|
||||||
class AdaptiveTitlePreferenceCategory(context: Context) : PreferenceCategory(context) {
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
if (title.isNullOrBlank()) {
|
|
||||||
holder.itemView.updateLayoutParams<RecyclerView.LayoutParams> {
|
|
||||||
height = 0
|
|
||||||
topMargin = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
|
|
||||||
class IntListPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
|
||||||
ListPreference(context, attrs) {
|
|
||||||
|
|
||||||
override fun persistString(value: String?): Boolean {
|
|
||||||
return value != null && persistInt(value.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getPersistedString(defaultReturnValue: String?): String? {
|
|
||||||
// When the underlying preference is using a PreferenceDataStore, there's no way (for now)
|
|
||||||
// to check if a value is in the store, so we use a most likely unused value as workaround
|
|
||||||
val defaultIntValue = Int.MIN_VALUE + 1
|
|
||||||
|
|
||||||
val value = getPersistedInt(defaultIntValue)
|
|
||||||
return if (value != defaultIntValue) {
|
|
||||||
value.toString()
|
|
||||||
} else {
|
|
||||||
defaultReturnValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.app.Dialog
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
|
||||||
import com.dd.processbutton.iml.ActionProcessButton
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import eu.kanade.domain.base.BasePreferences
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.PrefAccountLoginBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
abstract class LoginDialogPreference(
|
|
||||||
@StringRes private val usernameLabelRes: Int? = null,
|
|
||||||
bundle: Bundle? = null,
|
|
||||||
) : DialogController(bundle) {
|
|
||||||
|
|
||||||
var binding: PrefAccountLoginBinding? = null
|
|
||||||
private set
|
|
||||||
|
|
||||||
val preferences: BasePreferences by injectLazy()
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
|
||||||
binding = PrefAccountLoginBinding.inflate(LayoutInflater.from(activity!!))
|
|
||||||
onViewCreated(binding!!.root)
|
|
||||||
val titleName = activity!!.getString(getTitleName())
|
|
||||||
return MaterialAlertDialogBuilder(activity!!)
|
|
||||||
.setTitle(activity!!.getString(R.string.login_title, titleName))
|
|
||||||
.setView(binding!!.root)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
.create()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
|
||||||
if (usernameLabelRes != null) {
|
|
||||||
binding!!.usernameLabel.hint = view.context.getString(usernameLabelRes)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding!!.login.setMode(ActionProcessButton.Mode.ENDLESS)
|
|
||||||
binding!!.login.setOnClickListener { checkLogin() }
|
|
||||||
|
|
||||||
setCredentialsOnView(view)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
|
|
||||||
super.onChangeStarted(handler, type)
|
|
||||||
if (!type.isEnter) {
|
|
||||||
onDialogClosed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun onDialogClosed() {
|
|
||||||
binding = null
|
|
||||||
}
|
|
||||||
|
|
||||||
@StringRes
|
|
||||||
protected abstract fun getTitleName(): Int
|
|
||||||
|
|
||||||
protected abstract fun checkLogin()
|
|
||||||
|
|
||||||
protected abstract fun setCredentialsOnView(view: View)
|
|
||||||
}
|
|
|
@ -1,76 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
|
||||||
|
|
||||||
class ThemesPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
|
||||||
ListPreference(context, attrs),
|
|
||||||
ThemesPreferenceAdapter.OnItemClickListener {
|
|
||||||
|
|
||||||
private var recycler: RecyclerView? = null
|
|
||||||
private val adapter = ThemesPreferenceAdapter(this)
|
|
||||||
|
|
||||||
var lastScrollPosition: Int? = null
|
|
||||||
|
|
||||||
var entries: List<AppTheme> = emptyList()
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
adapter.setItems(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.pref_themes_list
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
|
|
||||||
recycler = holder.findViewById(R.id.themes_list) as RecyclerView
|
|
||||||
recycler?.layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
|
|
||||||
recycler?.adapter = adapter
|
|
||||||
|
|
||||||
// Retain scroll position on activity recreate after changing theme
|
|
||||||
recycler?.addOnScrollListener(
|
|
||||||
object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
super.onScrolled(recyclerView, dx, dy)
|
|
||||||
lastScrollPosition = recyclerView.computeHorizontalScrollOffset()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
lastScrollPosition?.let { scrollToOffset(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onItemClick(position: Int) {
|
|
||||||
if (position !in 0..entries.size) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
callChangeListener(value)
|
|
||||||
value = entries[position].name
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onClick() {
|
|
||||||
// no-op; not actually a DialogPreference
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun scrollToOffset(lX: Int) {
|
|
||||||
recycler?.let {
|
|
||||||
(it.layoutManager as LinearLayoutManager).apply {
|
|
||||||
scrollToPositionWithOffset(
|
|
||||||
// 114dp is the width of the pref_theme_item layout
|
|
||||||
lX / 114.dpToPx,
|
|
||||||
-lX % 114.dpToPx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
lastScrollPosition = it.computeHorizontalScrollOffset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.appcompat.view.ContextThemeWrapper
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import eu.kanade.domain.ui.UiPreferences
|
|
||||||
import eu.kanade.domain.ui.model.AppTheme
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.databinding.PrefThemeItemBinding
|
|
||||||
import eu.kanade.tachiyomi.ui.base.delegate.ThemingDelegate
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class ThemesPreferenceAdapter(private val clickListener: OnItemClickListener) :
|
|
||||||
RecyclerView.Adapter<ThemesPreferenceAdapter.ThemeViewHolder>() {
|
|
||||||
|
|
||||||
private val preferences: UiPreferences by injectLazy()
|
|
||||||
|
|
||||||
private var themes = emptyList<AppTheme>()
|
|
||||||
|
|
||||||
private lateinit var binding: PrefThemeItemBinding
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ThemeViewHolder {
|
|
||||||
val themeResIds = ThemingDelegate.getThemeResIds(themes[viewType], preferences.themeDarkAmoled().get())
|
|
||||||
val themedContext = themeResIds.fold(parent.context) {
|
|
||||||
context, themeResId ->
|
|
||||||
ContextThemeWrapper(context, themeResId)
|
|
||||||
}
|
|
||||||
|
|
||||||
binding = PrefThemeItemBinding.inflate(LayoutInflater.from(themedContext), parent, false)
|
|
||||||
return ThemeViewHolder(binding.root)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemViewType(position: Int): Int = position
|
|
||||||
|
|
||||||
override fun getItemCount(): Int = themes.size
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ThemesPreferenceAdapter.ThemeViewHolder, position: Int) {
|
|
||||||
holder.bind(themes[position])
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setItems(themes: List<AppTheme>) {
|
|
||||||
this.themes = themes
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class ThemeViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
|
|
||||||
|
|
||||||
private val selectedColor = view.context.getResourceColor(R.attr.colorAccent)
|
|
||||||
private val unselectedColor = view.context.getResourceColor(android.R.attr.divider)
|
|
||||||
|
|
||||||
fun bind(appTheme: AppTheme) {
|
|
||||||
binding.name.text = view.context.getString(appTheme.titleResId!!)
|
|
||||||
|
|
||||||
// For rounded corners
|
|
||||||
binding.badges.clipToOutline = true
|
|
||||||
|
|
||||||
val isSelected = preferences.appTheme().get() == appTheme
|
|
||||||
binding.themeCard.isChecked = isSelected
|
|
||||||
binding.themeCard.strokeColor = if (isSelected) selectedColor else unselectedColor
|
|
||||||
|
|
||||||
listOf(binding.root, binding.themeCard).forEach {
|
|
||||||
it.setOnClickListener {
|
|
||||||
clickListener.onItemClick(bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface OnItemClickListener {
|
|
||||||
fun onItemClick(position: Int)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.widget.preference
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.widget.ImageView
|
|
||||||
import androidx.annotation.ColorInt
|
|
||||||
import androidx.core.view.isVisible
|
|
||||||
import androidx.preference.Preference
|
|
||||||
import androidx.preference.PreferenceViewHolder
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
|
|
||||||
class TrackerPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
|
||||||
Preference(context, attrs) {
|
|
||||||
|
|
||||||
init {
|
|
||||||
layoutResource = R.layout.pref_tracker_item
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
|
||||||
super.onBindViewHolder(holder)
|
|
||||||
|
|
||||||
val logoContainer = holder.findViewById(R.id.logo_container) as MaterialCardView
|
|
||||||
val checkedIcon = holder.findViewById(R.id.checked_icon) as ImageView
|
|
||||||
|
|
||||||
logoContainer.setCardBackgroundColor(iconColor)
|
|
||||||
checkedIcon.isVisible = !getPersistedString("").isNullOrEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
@ColorInt
|
|
||||||
var iconColor: Int = Color.TRANSPARENT
|
|
||||||
set(value) {
|
|
||||||
field = value
|
|
||||||
notifyChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun notifyChanged() {
|
|
||||||
super.notifyChanged()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M19.35,10.04C18.67,6.59 15.64,4 12,4c-1.48,0 -2.85,0.43 -4.01,1.17l1.46,1.46C10.21,6.23 11.08,6 12,6c3.04,0 5.5,2.46 5.5,5.5v0.5H19c1.66,0 3,1.34 3,3 0,1.13 -0.64,2.11 -1.56,2.62l1.45,1.45C23.16,18.16 24,16.68 24,15c0,-2.64 -2.05,-4.78 -4.65,-4.96zM3,5.27l2.75,2.74C2.56,8.15 0,10.77 0,14c0,3.31 2.69,6 6,6h11.73l2,2L21,20.73 4.27,4 3,5.27zM7.73,10l8,8H6c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4h1.73z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FF4CAF50"
|
|
||||||
android:pathData="M9,16.2L4.8,12l-1.4,1.4L9,19 21,7l-1.4,-1.4L9,16.2z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M9,7L7,7v2h2L9,7zM9,11L7,11v2h2v-2zM9,3c-1.11,0 -2,0.9 -2,2h2L9,3zM13,15h-2v2h2v-2zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM13,3h-2v2h2L13,3zM9,17v-2L7,15c0,1.1 0.89,2 2,2zM19,13h2v-2h-2v2zM19,9h2L21,7h-2v2zM19,17c1.1,0 2,-0.9 2,-2h-2v2zM5,7L3,7v12c0,1.1 0.89,2 2,2h12v-2L5,19L5,7zM15,5h2L17,3h-2v2zM15,17h2v-2h-2v2z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M7,13H17V11H7ZM5,21Q4.175,21 3.587,20.413Q3,19.825 3,19V5Q3,4.175 3.587,3.587Q4.175,3 5,3H19Q19.825,3 20.413,3.587Q21,4.175 21,5V19Q21,19.825 20.413,20.413Q19.825,21 19,21Z"/>
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M12,4L12,1L8,5l4,4L12,6c3.31,0 6,2.69 6,6 0,1.01 -0.25,1.97 -0.7,2.8l1.46,1.46C19.54,15.03 20,13.57 20,12c0,-4.42 -3.58,-8 -8,-8zM12,18c-3.31,0 -6,-2.69 -6,-6 0,-1.01 0.25,-1.97 0.7,-2.8L5.24,7.74C4.46,8.97 4,10.43 4,12c0,4.42 3.58,8 8,8v3l4,-4 -4,-4v3z" />
|
|
||||||
</vector>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/black"
|
|
||||||
android:pathData="M4,11h5L9,5L4,5v6zM4,18h5v-6L4,12v6zM10,18h5v-6h-5v6zM16,18h5v-6h-5v6zM10,11h5L15,5h-5v6zM16,5v6h5L21,5h-5z" />
|
|
||||||
</vector>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<gradient
|
|
||||||
android:angle="90"
|
|
||||||
android:endColor="#00ffffff"
|
|
||||||
android:startColor="#ffffffff" />
|
|
||||||
|
|
||||||
<corners android:radius="0dp" />
|
|
||||||
</shape>
|
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<gradient
|
|
||||||
android:angle="90"
|
|
||||||
android:endColor="#00000000"
|
|
||||||
android:centerColor="#CC000000"
|
|
||||||
android:startColor="#E6000000" />
|
|
||||||
|
|
||||||
<corners android:radius="0dp" />
|
|
||||||
</shape>
|
|
|
@ -1,14 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<gradient
|
|
||||||
android:type="radial"
|
|
||||||
android:gradientRadius="18dp"
|
|
||||||
android:startColor="#CC000000"
|
|
||||||
android:centerColor="#CC000000"
|
|
||||||
android:endColor="#0D000000" />
|
|
||||||
|
|
||||||
<corners android:radius="0dp" />
|
|
||||||
|
|
||||||
</shape>
|
|
|
@ -1,18 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
style="?attr/materialAlertDialogBodyTextStyle"
|
|
||||||
android:id="@+id/quad_state_control"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="?attr/selectableItemBackground"
|
|
||||||
android:minHeight="?attr/listPreferredItemHeightSmall"
|
|
||||||
android:gravity="start|center_vertical"
|
|
||||||
android:textAlignment="viewStart"
|
|
||||||
android:paddingStart="@dimen/abc_select_dialog_padding_start_material"
|
|
||||||
android:paddingEnd="?attr/dialogPreferredPadding"
|
|
||||||
android:drawablePadding="20dp"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
app:drawableStartCompat="@drawable/ic_check_box_outline_blank_24dp"
|
|
||||||
tools:text="Quad-state item" />
|
|
|
@ -1,49 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:minHeight="48dp">
|
|
||||||
|
|
||||||
<Space
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="@dimen/abc_dialog_title_divider_material" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/message"
|
|
||||||
style="?attr/materialAlertDialogBodyTextStyle"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingBottom="@dimen/abc_dialog_title_divider_material"
|
|
||||||
android:paddingHorizontal="?attr/dialogPreferredPadding"
|
|
||||||
android:visibility="gone"
|
|
||||||
tools:text="Dialog Message for quad-state dialog"
|
|
||||||
tools:visibility="visible" />
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
|
||||||
android:id="@+id/scrollIndicatorUp"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="top" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:scrollIndicators="none"
|
|
||||||
tools:listitem="@layout/dialog_quadstatemultichoice_item" />
|
|
||||||
|
|
||||||
<com.google.android.material.divider.MaterialDivider
|
|
||||||
android:id="@+id/scrollIndicatorDown"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_gravity="bottom" />
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="24dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:id="@+id/username_label"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/username">
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
|
||||||
android:id="@+id/username"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="text" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="@string/password"
|
|
||||||
app:endIconMode="password_toggle">
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="textPassword" />
|
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
|
||||||
|
|
||||||
<com.dd.processbutton.iml.ActionProcessButton
|
|
||||||
android:id="@+id/login"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="20dp"
|
|
||||||
android:text="@string/login"
|
|
||||||
android:textColor="?attr/colorOnPrimary"
|
|
||||||
app:pb_colorNormal="?attr/colorPrimary"
|
|
||||||
app:pb_colorPressed="?attr/colorPrimary"
|
|
||||||
app:pb_textComplete="@string/login_success"
|
|
||||||
app:pb_textError="@string/invalid_login"
|
|
||||||
app:pb_textProgress="@string/loading" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,51 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:baselineAligned="false"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/portrait" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MinMaxNumberPicker
|
|
||||||
android:id="@+id/portrait_columns"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:max="10"
|
|
||||||
app:min="0" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="center"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/landscape" />
|
|
||||||
|
|
||||||
<eu.kanade.tachiyomi.widget.MinMaxNumberPicker
|
|
||||||
android:id="@+id/landscape_columns"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
app:max="10"
|
|
||||||
app:min="0" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,148 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="114dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:padding="4dp">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/theme_card"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:checkable="true"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
app:cardCornerRadius="17dp"
|
|
||||||
app:strokeColor="?attr/colorAccent"
|
|
||||||
app:strokeWidth="4dp"
|
|
||||||
app:cardElevation="0dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="170dp"
|
|
||||||
android:background="?android:attr/colorBackground">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:id="@+id/top_nav"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/top_nav_text"
|
|
||||||
android:layout_width="54dp"
|
|
||||||
android:layout_height="17dp"
|
|
||||||
android:layout_marginTop="5dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/top_nav"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/top_nav"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/top_nav"
|
|
||||||
app:cardBackgroundColor="?attr/colorOnSurface"
|
|
||||||
app:cardCornerRadius="4dp"/>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/cover_container"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/top_nav"
|
|
||||||
app:layout_constraintDimensionRatio="2:2.7"
|
|
||||||
app:layout_constraintStart_toStartOf="@id/top_nav_text"
|
|
||||||
app:layout_constraintEnd_toStartOf="@id/center_guideline"
|
|
||||||
app:cardBackgroundColor="?android:attr/divider"
|
|
||||||
app:cardElevation="0dp" />
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
app:layout_constraintStart_toStartOf="@+id/cover_container"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/cover_container"
|
|
||||||
app:cardCornerRadius="6dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/badges"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@drawable/rounded_rectangle">
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:background="?attr/colorTertiary" />
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="12dp"
|
|
||||||
android:layout_height="16dp"
|
|
||||||
android:background="?attr/colorSecondary" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/bottom_nav"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:cardCornerRadius="0dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="2dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingHorizontal="12dp">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/bottom_nav_selected_item"
|
|
||||||
android:layout_width="17dp"
|
|
||||||
android:layout_height="17dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
app:cardBackgroundColor="?attr/colorPrimary"
|
|
||||||
app:cardCornerRadius="100dp"/>
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/bottom_nav_unselected_item"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="17dp"
|
|
||||||
android:alpha="0.6"
|
|
||||||
app:cardBackgroundColor="?attr/colorOnSurface"
|
|
||||||
app:cardCornerRadius="4dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.Guideline
|
|
||||||
android:id="@+id/center_guideline"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
app:layout_constraintGuide_percent="0.5" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/name"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="32sp"
|
|
||||||
android:maxLines="2"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:textAlignment="center"
|
|
||||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
|
||||||
android:scrollbars="none"
|
|
||||||
tools:text="Theme Name" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,26 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@android:id/title"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingTop="4dp"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Menu"
|
|
||||||
tools:text="App theme" />
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/themes_list"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:paddingHorizontal="12dp"
|
|
||||||
android:paddingVertical="8dp"
|
|
||||||
android:clipToPadding="false"
|
|
||||||
tools:listitem="@layout/pref_theme_item" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,49 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="?listPreferredItemHeight"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:paddingVertical="8dp">
|
|
||||||
|
|
||||||
<com.google.android.material.card.MaterialCardView
|
|
||||||
android:id="@+id/logo_container"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
app:cardBackgroundColor="#2E51A2"
|
|
||||||
app:cardElevation="0dp"
|
|
||||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.MaterialCardView.Tracker">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@android:id/icon"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:importantForAccessibility="no"
|
|
||||||
android:padding="4dp"
|
|
||||||
tools:src="@drawable/ic_tracker_mal" />
|
|
||||||
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@android:id/title"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:ellipsize="end"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:paddingHorizontal="16dp"
|
|
||||||
android:textAppearance="?attr/textAppearanceTitleMedium"
|
|
||||||
tools:text="MyAnimeList" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/checked_icon"
|
|
||||||
android:layout_width="32dp"
|
|
||||||
android:layout_height="32dp"
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:padding="4dp"
|
|
||||||
android:src="@drawable/ic_done_green_24dp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
|
@ -1,19 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_select_all"
|
|
||||||
android:icon="@drawable/ic_select_all_24dp"
|
|
||||||
android:title="@string/action_select_all"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_select_inverse"
|
|
||||||
android:icon="@drawable/ic_flip_to_back_24dp"
|
|
||||||
android:title="@string/action_select_inverse"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_backup_help"
|
|
||||||
android:icon="@drawable/ic_help_24dp"
|
|
||||||
android:title="@string/label_help"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
|
@ -1,11 +0,0 @@
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/action_tracking_help"
|
|
||||||
android:icon="@drawable/ic_help_24dp"
|
|
||||||
android:title="@string/tracking_guide"
|
|
||||||
app:iconTint="?attr/colorOnSurface"
|
|
||||||
app:showAsAction="ifRoom" />
|
|
||||||
|
|
||||||
</menu>
|
|
|
@ -56,7 +56,6 @@ natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
|
||||||
markwon = "io.noties.markwon:core:4.6.2"
|
markwon = "io.noties.markwon:core:4.6.2"
|
||||||
|
|
||||||
material = "com.google.android.material:material:1.7.0-rc01"
|
material = "com.google.android.material:material:1.7.0-rc01"
|
||||||
androidprocessbutton = "com.github.dmytrodanylyk.android-process-button:library:1.0.4"
|
|
||||||
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
flexible-adapter-core = "com.github.arkon.FlexibleAdapter:flexible-adapter:c8013533"
|
||||||
flexible-adapter-ui = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui:c8013533"
|
flexible-adapter-ui = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui:c8013533"
|
||||||
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
photoview = "com.github.chrisbanes:PhotoView:2.3.0"
|
||||||
|
|
Reference in a new issue