Remove dead code

Mostly from settings rewrite, but some other things too.

(cherry picked from commit 69cdba71eb842865586309d8549de78480cdbe0e)

# Conflicts:
#	app/src/main/java/eu/kanade/presentation/more/settings/database/ClearDatabaseScreen.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAppearanceController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBackupController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsBrowseController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsGeneralController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsReaderController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsSecurityController.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/database/ClearDatabasePresenter.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/search/SettingsSearchHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/setting/track/TrackLoginDialog.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/materialdialogs/MaterialAlertDialogBuilderExtensions.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/preference/IntListPreference.kt
#	app/src/main/java/eu/kanade/tachiyomi/widget/preference/LoginDialogPreference.kt
#	app/src/main/res/drawable/ic_done_green_24dp.xml
#	app/src/main/res/layout/pref_account_login.xml
#	app/src/main/res/layout/pref_library_columns.xml
This commit is contained in:
arkon 2022-10-16 12:40:56 -04:00 committed by Jobobby04
parent a2f6b90547
commit 2144221250
64 changed files with 6 additions and 7416 deletions

View File

@ -240,7 +240,6 @@ dependencies {
// UI libraries
implementation(libs.material)
implementation(libs.androidprocessbutton)
implementation(libs.flexible.adapter.core)
implementation(libs.flexible.adapter.ui)
implementation(libs.photoview)

View File

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

View File

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

View File

@ -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, /* SY --> */ it /* SY <-- */)
presenter.clearSelection()
presenter.dialog = null
context.toast(R.string.clear_database_completed)
},
)
}
}

View File

@ -6,14 +6,13 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import eu.kanade.domain.source.model.SourceWithCount
import eu.kanade.tachiyomi.ui.setting.database.ClearDatabasePresenter
@Stable
interface ClearDatabaseState {
val items: List<SourceWithCount>
val selection: List<Long>
val isEmpty: Boolean
var dialog: ClearDatabasePresenter.Dialog?
var dialog: Dialog?
}
fun ClearDatabaseState(): ClearDatabaseState {
@ -24,5 +23,9 @@ class ClearDatabaseStateImpl : ClearDatabaseState {
override var items: List<SourceWithCount> by mutableStateOf(emptyList())
override var selection: List<Long> by mutableStateOf(emptyList())
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()
}

View File

@ -1,663 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.webkit.WebStorage
import android.webkit.WebView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.net.toUri
import androidx.core.text.HtmlCompat
import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.manga.interactor.GetAllManga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.service.SourcePreferences
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.cache.PagePreviewCache
import eu.kanade.tachiyomi.data.download.DownloadManager
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.source.SourceManager
import eu.kanade.tachiyomi.source.SourceManager.Companion.DELEGATED_SOURCES
import eu.kanade.tachiyomi.ui.base.controller.DialogController
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.launchIO
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.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.DeviceUtil
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 exh.debug.SettingsDebugController
import exh.log.EHLogLevel
import exh.pref.DelegateSourcePreferences
import exh.source.BlacklistedSources
import exh.source.EH_SOURCE_ID
import exh.source.EXH_SOURCE_ID
import kotlinx.coroutines.Job
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()
private val sourcePreferences: SourcePreferences by injectLazy()
private val delegateSourcePreferences: DelegateSourcePreferences by injectLazy()
private val unsortedPreferences: UnsortedPreferences by injectLazy()
private val getAllManga: GetAllManga by injectLazy()
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
private val pagePreviewCache: PagePreviewCache by injectLazy()
@SuppressLint("BatteryLife")
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_advanced
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() }
}
// SY -->
preference {
key = CLEAR_PREVIEW_CACHE_KEY
titleRes = R.string.pref_clear_page_preview_cache
summary = context.getString(R.string.used_cache, pagePreviewCache.readableSize)
onClick { clearPagePreviewCache() }
}
// SY <--
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
}
}
}
// --> EXH
preferenceCategory {
titleRes = R.string.download_notifier_downloader_title
preference {
key = "clean_up_downloaded_chapters"
titleRes = R.string.clean_up_downloaded_chapters
summaryRes = R.string.delete_unused_chapters
onClick {
val ctrl = CleanupDownloadsDialogController()
ctrl.targetController = this@SettingsAdvancedController
ctrl.showDialog(router)
}
}
}
preferenceCategory {
titleRes = R.string.data_saver
switchPreference {
bindTo(sourcePreferences.dataSaver())
titleRes = R.string.data_saver
summaryRes = R.string.data_saver_summary
}
editTextPreference {
bindTo(sourcePreferences.dataSaverServer())
titleRes = R.string.data_saver_server
summaryRes = R.string.data_saver_server_summary
visibleIf(sourcePreferences.dataSaver()) { it }
}
switchPreference {
bindTo(sourcePreferences.dataSaverDownloader())
titleRes = R.string.data_saver_downloader
visibleIf(sourcePreferences.dataSaver()) { it }
}
switchPreference {
bindTo(sourcePreferences.dataSaverIgnoreJpeg())
titleRes = R.string.data_saver_ignore_jpeg
visibleIf(sourcePreferences.dataSaver()) { it }
}
switchPreference {
bindTo(sourcePreferences.dataSaverIgnoreGif())
titleRes = R.string.data_saver_ignore_gif
visibleIf(sourcePreferences.dataSaver()) { it }
}
intListPreference {
bindTo(sourcePreferences.dataSaverImageQuality())
titleRes = R.string.data_saver_image_quality
entries = arrayOf("10%", "20%", "40%", "50%", "70%", "80%", "90%", "95%")
entryValues = entries.map { it.trimEnd('%') }.toTypedArray()
summaryRes = R.string.data_saver_image_quality_summary
visibleIf(sourcePreferences.dataSaver()) { it }
}
switchPreference {
bindTo(sourcePreferences.dataSaverImageFormatJpeg())
titleRes = R.string.data_saver_image_format
summaryOn = context.getString(R.string.data_saver_image_format_summary_on)
summaryOff = context.getString(R.string.data_saver_image_format_summary_off)
visibleIf(sourcePreferences.dataSaver()) { it }
}
switchPreference {
bindTo(sourcePreferences.dataSaverColorBW())
titleRes = R.string.data_saver_color_bw
visibleIf(sourcePreferences.dataSaver()) { it }
}
}
preferenceCategory {
titleRes = R.string.developer_tools
isPersistent = false
switchPreference {
bindTo(unsortedPreferences.isHentaiEnabled())
titleRes = R.string.toggle_hentai_features
summaryRes = R.string.toggle_hentai_features_summary
onChange {
if (unsortedPreferences.isHentaiEnabled().get()) {
BlacklistedSources.HIDDEN_SOURCES += EH_SOURCE_ID
BlacklistedSources.HIDDEN_SOURCES += EXH_SOURCE_ID
} else {
BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID
BlacklistedSources.HIDDEN_SOURCES -= EXH_SOURCE_ID
}
true
}
}
switchPreference {
bindTo(delegateSourcePreferences.delegateSources())
titleRes = R.string.toggle_delegated_sources
summary = context.getString(R.string.toggle_delegated_sources_summary, context.getString(R.string.app_name), DELEGATED_SOURCES.values.map { it.sourceName }.distinct().joinToString())
}
intListPreference {
bindTo(unsortedPreferences.logLevel())
titleRes = R.string.log_level
entries = EHLogLevel.values().map {
"${context.getString(it.nameRes)} (${context.getString(it.description)})"
}.toTypedArray()
entryValues = EHLogLevel.values().mapIndexed { index, _ -> "$index" }.toTypedArray()
summaryRes = R.string.log_level_summary
}
switchPreference {
bindTo(sourcePreferences.enableSourceBlacklist())
titleRes = R.string.enable_source_blacklist
summary = context.getString(R.string.enable_source_blacklist_summary, context.getString(R.string.app_name))
}
preference {
key = "pref_open_debug_menu"
titleRes = R.string.open_debug_menu
summary = HtmlCompat.fromHtml(context.getString(R.string.open_debug_menu_summary), HtmlCompat.FROM_HTML_MODE_LEGACY)
onClick { router.pushController(SettingsDebugController()) }
}
}
// <-- EXH
}
// SY -->
class CleanupDownloadsDialogController : DialogController() {
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val options = activity!!.resources.getStringArray(R.array.clean_up_downloads)
val selected = options.map { true }.toBooleanArray()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.clean_up_downloaded_chapters)
.setMultiChoiceItems(options, selected) { dialog, which, checked ->
if (which == 0) {
(dialog as AlertDialog).listView.setItemChecked(which, true)
} else {
selected[which] = checked
}
}
.setPositiveButton(android.R.string.ok) { _, _ ->
(targetController as? SettingsAdvancedController)?.cleanupDownloads(
selected[1],
selected[2],
)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}
private fun cleanupDownloads(removeRead: Boolean, removeNonFavorite: Boolean) {
if (job?.isActive == true) return
activity?.toast(R.string.starting_cleanup)
job = launchIO {
val mangaList = getAllManga.await()
val downloadManager: DownloadManager = Injekt.get()
var foldersCleared = 0
Injekt.get<SourceManager>().getOnlineSources().forEach { source ->
val mangaFolders = downloadManager.getMangaFolders(source)
val sourceManga = mangaList
.asSequence()
.filter { it.source == source.id }
.map { it to DiskUtil.buildValidFilename(it.ogTitle) }
.toList()
mangaFolders.forEach mangaFolder@{ mangaFolder ->
val manga = sourceManga.find { (_, folderName) -> folderName == mangaFolder.name }?.first
if (manga == null) {
// download is orphaned delete it
foldersCleared += 1 + (mangaFolder.listFiles().orEmpty().size)
mangaFolder.delete()
} else {
val chapterList = getChapterByMangaId.await(manga.id)
foldersCleared += downloadManager.cleanupChapters(chapterList.map { it.toDbChapter() }, manga, source, removeRead, removeNonFavorite)
}
}
}
withUIContext {
val activity = activity ?: return@withUIContext
val cleanupString =
if (foldersCleared == 0) {
activity.getString(R.string.no_folders_to_cleanup)
} else {
resources!!.getQuantityString(
R.plurals.cleanup_done,
foldersCleared,
foldersCleared,
)
}
activity.toast(cleanupString, Toast.LENGTH_LONG)
}
}
}
private fun clearPagePreviewCache() {
val activity = activity ?: return
launchIO {
try {
val deletedFiles = pagePreviewCache.clear()
withUIContext {
activity.toast(resources?.getString(R.string.cache_deleted, deletedFiles))
findPreference(CLEAR_PREVIEW_CACHE_KEY)?.summary =
resources?.getString(R.string.used_cache, pagePreviewCache.readableSize)
}
} catch (e: Throwable) {
logcat(LogPriority.ERROR, e)
withUIContext { activity.toast(R.string.cache_delete_error) }
}
}
}
// SY <--
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 companion object {
// SY -->
private var job: Job? = null
// SY <--
}
}
private const val CLEAR_CACHE_KEY = "pref_clear_cache_key"
// SY -->
private const val CLEAR_PREVIEW_CACHE_KEY = "pref_clear_preview_cache_key"
// SY <--

View File

@ -1,190 +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"
}
}
preferenceCategory {
titleRes = R.string.pref_category_navbar
switchPreference {
bindTo(uiPreferences.showNavUpdates())
titleRes = R.string.pref_hide_updates_button
}
switchPreference {
bindTo(uiPreferences.showNavHistory())
titleRes = R.string.pref_hide_history_button
}
switchPreference {
bindTo(uiPreferences.bottomBarLabels())
titleRes = R.string.pref_show_bottom_bar_labels
}
}
}
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"

View File

@ -1,314 +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,
// SY -->
R.string.custom_manga_info,
R.string.all_read_manga,
// SY <--
)
.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
// SY -->
5 -> flags = flags or BackupConst.BACKUP_CUSTOM_INFO
6 -> flags = flags or BackupConst.BACKUP_READ_MANGA
// SY <--
}
}
}
(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/"

View File

@ -1,148 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import androidx.fragment.app.FragmentActivity
import androidx.preference.PreferenceScreen
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.extension.ExtensionUpdateJob
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.category.repos.RepoController
import eu.kanade.tachiyomi.ui.category.sources.SourceCategoryController
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.onClick
import eu.kanade.tachiyomi.util.preference.preference
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()
// SY -->
private val uiPreferences: UiPreferences by injectLazy()
private val unsortedPreferences: UnsortedPreferences by injectLazy()
// SY <--
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.browse
// SY -->
preferenceCategory {
titleRes = R.string.label_sources
preference {
key = "pref_edit_source_categories"
titleRes = R.string.action_edit_categories
val catCount = sourcePreferences.sourcesTabCategories().get().count()
summary = context.resources.getQuantityString(R.plurals.num_categories, catCount, catCount)
onClick {
router.pushController(SourceCategoryController())
}
}
switchPreference {
bindTo(sourcePreferences.sourcesTabCategoriesFilter())
titleRes = R.string.pref_source_source_filtering
summaryRes = R.string.pref_source_source_filtering_summery
}
switchPreference {
bindTo(uiPreferences.useNewSourceNavigation())
titleRes = R.string.pref_source_navigation
summaryRes = R.string.pref_source_navigation_summery
}
switchPreference {
bindTo(unsortedPreferences.allowLocalSourceHiddenFolders())
titleRes = R.string.pref_local_source_hidden_folders
summaryRes = R.string.pref_local_source_hidden_folders_summery
}
}
preferenceCategory {
titleRes = R.string.feed
switchPreference {
bindTo(uiPreferences.feedTabInFront())
titleRes = R.string.pref_feed_position
summaryRes = R.string.pref_feed_position_summery
}
}
// SY <--
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
}
}
// SY -->
preference {
key = "pref_edit_extension_repos"
titleRes = R.string.action_edit_repos
val catCount = unsortedPreferences.extensionRepos().get().count()
summary = context.resources.getQuantityString(R.plurals.num_repos, catCount, catCount)
onClick {
router.pushController(RepoController())
}
}
// SY <--
}
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)
}
}
}

View File

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

View File

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

View File

@ -1,614 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.text.InputType
import android.view.LayoutInflater
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.widget.doAfterTextChanged
import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.domain.manga.interactor.DeleteFavoriteEntries
import eu.kanade.domain.manga.interactor.GetExhFavoriteMangaWithMetadata
import eu.kanade.domain.manga.interactor.GetFlatMetadataById
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.preference.Preference
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
import eu.kanade.tachiyomi.ui.setting.eh.FrontPageCategoriesDialog
import eu.kanade.tachiyomi.ui.setting.eh.LanguagesDialog
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
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.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.util.system.toast
import exh.eh.EHentaiUpdateWorker
import exh.eh.EHentaiUpdateWorkerConstants
import exh.eh.EHentaiUpdaterStats
import exh.favorites.FavoritesIntroDialog
import exh.log.xLogD
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.uconfig.WarnConfigureDialogController
import exh.ui.login.EhLoginActivity
import exh.util.nullIfBlank
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import uy.kohesive.injekt.injectLazy
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
/**
* EH Settings fragment
*/
class SettingsEhController : SettingsController() {
val unsortedPreferences: UnsortedPreferences by injectLazy()
private val getFlatMetadataById: GetFlatMetadataById by injectLazy()
private val deleteFavoriteEntries: DeleteFavoriteEntries by injectLazy()
private val getExhFavoriteMangaWithMetadata: GetExhFavoriteMangaWithMetadata by injectLazy()
fun Preference<*>.reconfigure(): Boolean {
// Listen for change commit
changes()
.take(1) // Only listen for first commit
.onEach {
// Only listen for first change commit
WarnConfigureDialogController.uploadSettings(router)
}
.launchIn(viewScope)
// Always return true to save changes
return true
}
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.pref_category_eh
preferenceCategory {
titleRes = R.string.ehentai_prefs_account_settings
switchPreference {
bindTo(unsortedPreferences.enableExhentai())
titleRes = R.string.enable_exhentai
summaryOff = context.getString(R.string.requires_login)
isPersistent = false
unsortedPreferences.enableExhentai()
.changes()
.onEach {
isChecked = it
}
.launchIn(viewScope)
onChange { newVal ->
newVal as Boolean
if (!newVal) {
unsortedPreferences.enableExhentai().set(false)
true
} else {
startActivityForResult(EhLoginActivity.newIntent(activity!!), LOGIN_RESULT)
false
}
}
}
intListPreference {
bindTo(unsortedPreferences.useHentaiAtHome())
titleRes = R.string.use_hentai_at_home
summaryRes = R.string.use_hentai_at_home_summary
entriesRes = arrayOf(
R.string.use_hentai_at_home_option_1,
R.string.use_hentai_at_home_option_2,
)
entryValues = arrayOf("0", "1")
onChange { unsortedPreferences.useHentaiAtHome().reconfigure() }
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
switchPreference {
bindTo(unsortedPreferences.useJapaneseTitle())
titleRes = R.string.show_japanese_titles
summaryOn = context.getString(R.string.show_japanese_titles_option_1)
summaryOff = context.getString(R.string.show_japanese_titles_option_2)
onChange { unsortedPreferences.useJapaneseTitle().reconfigure() }
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
switchPreference {
bindTo(unsortedPreferences.exhUseOriginalImages())
titleRes = R.string.use_original_images
summaryOn = context.getString(R.string.use_original_images_on)
summaryOff = context.getString(R.string.use_original_images_off)
onChange { unsortedPreferences.exhUseOriginalImages().reconfigure() }
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
preference {
key = "pref_watched_tags"
titleRes = R.string.watched_tags
summaryRes = R.string.watched_tags_summary
onClick {
val intent = if (unsortedPreferences.enableExhentai().get()) {
WebViewActivity.newIntent(activity!!, url = "https://exhentai.org/mytags", title = context.getString(R.string.watched_tags_exh))
} else {
WebViewActivity.newIntent(activity!!, url = "https://e-hentai.org/mytags", title = context.getString(R.string.watched_tags_eh))
}
startActivity(intent)
}
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
preference {
bindTo(unsortedPreferences.ehTagFilterValue())
titleRes = R.string.tag_filtering_threshold
summary = context.getString(R.string.tag_filtering_threshhold_summary, unsortedPreferences.ehTagFilterValue().get())
onClick {
var value: Int? = unsortedPreferences.ehTagFilterValue().get()
MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.tag_filtering_threshold)
.let { builder ->
val binding = DialogStubTextinputBinding.inflate(LayoutInflater.from(builder.context))
binding.textField.editText?.apply {
inputType = InputType.TYPE_NUMBER_FLAG_SIGNED
setText(value.toString(), TextView.BufferType.EDITABLE)
doAfterTextChanged {
value = it?.toString()?.toIntOrNull()
this@SettingsEhController.xLogD(value)
error = if (value in -9999..0 || it.toString() == "-") {
null
} else {
context.getString(R.string.tag_filtering_threshhold_error)
}
}
post {
requestFocusFromTouch()
context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
}
}
builder.setView(binding.root)
}
.setPositiveButton(android.R.string.ok) { _, _ ->
unsortedPreferences.ehTagFilterValue().set(value ?: return@setPositiveButton)
summary = context.getString(R.string.tag_filtering_threshhold_summary, unsortedPreferences.ehTagFilterValue().get())
unsortedPreferences.ehTagFilterValue().reconfigure()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
preference {
bindTo(unsortedPreferences.ehTagWatchingValue())
titleRes = R.string.tag_watching_threshhold
summary = context.getString(R.string.tag_watching_threshhold_summary, unsortedPreferences.ehTagWatchingValue().get())
onClick {
var value: Int? = unsortedPreferences.ehTagWatchingValue().get()
MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.tag_watching_threshhold)
.let { builder ->
val binding = DialogStubTextinputBinding.inflate(LayoutInflater.from(builder.context))
binding.textField.editText?.apply {
inputType = InputType.TYPE_NUMBER_FLAG_SIGNED
setText(value.toString(), TextView.BufferType.EDITABLE)
doAfterTextChanged {
value = it?.toString()?.toIntOrNull()
error = if (value in 0..9999) {
null
} else {
context.getString(R.string.tag_watching_threshhold_error)
}
}
post {
requestFocusFromTouch()
context.getSystemService<InputMethodManager>()?.showSoftInput(this, 0)
}
}
builder.setView(binding.root)
}
.setPositiveButton(android.R.string.ok) { _, _ ->
unsortedPreferences.ehTagWatchingValue().set(value ?: return@setPositiveButton)
summary = context.getString(R.string.tag_watching_threshhold_summary, unsortedPreferences.ehTagWatchingValue().get())
unsortedPreferences.ehTagWatchingValue().reconfigure()
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
preference {
bindTo(unsortedPreferences.exhSettingsLanguages())
titleRes = R.string.language_filtering
summaryRes = R.string.language_filtering_summary
onClick {
val dialog = LanguagesDialog()
dialog.targetController = this@SettingsEhController
dialog.showDialog(router)
}
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
preference {
bindTo(unsortedPreferences.exhEnabledCategories())
titleRes = R.string.frong_page_categories
summaryRes = R.string.fromt_page_categories_summary
onClick {
val dialog = FrontPageCategoriesDialog()
dialog.targetController = this@SettingsEhController
dialog.showDialog(router)
}
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
switchPreference {
bindTo(unsortedPreferences.exhWatchedListDefaultState())
titleRes = R.string.watched_list_default
summaryRes = R.string.watched_list_state_summary
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
listPreference {
bindTo(unsortedPreferences.imageQuality())
summaryRes = R.string.eh_image_quality_summary
titleRes = R.string.eh_image_quality
entriesRes = arrayOf(
R.string.eh_image_quality_auto,
R.string.eh_image_quality_2400,
R.string.eh_image_quality_1600,
R.string.eh_image_quality_1280,
R.string.eh_image_quality_980,
R.string.eh_image_quality_780,
)
entryValues = arrayOf(
"auto",
"ovrs_2400",
"ovrs_1600",
"high",
"med",
"low",
)
onChange { unsortedPreferences.imageQuality().reconfigure() }
visibleIf(unsortedPreferences.enableExhentai()) { it }
}
switchPreference {
bindTo(unsortedPreferences.enhancedEHentaiView())
titleRes = R.string.pref_enhanced_e_hentai_view
summaryRes = R.string.pref_enhanced_e_hentai_view_summary
}
}
preferenceCategory {
titleRes = R.string.favorites_sync
switchPreference {
bindTo(unsortedPreferences.exhReadOnlySync())
titleRes = R.string.disable_favorites_uploading
summaryRes = R.string.disable_favorites_uploading_summary
}
preference {
key = "pref_show_sync_favorite_notes"
titleRes = R.string.show_favorite_sync_notes
summaryRes = R.string.show_favorite_sync_notes_summary
onClick {
activity?.let {
FavoritesIntroDialog().show(it)
}
}
}
switchPreference {
bindTo(unsortedPreferences.exhLenientSync())
titleRes = R.string.ignore_sync_errors
summaryRes = R.string.ignore_sync_errors_summary
}
preference {
key = "pref_force_sync_reset"
titleRes = R.string.force_sync_state_reset
summaryRes = R.string.force_sync_state_reset_summary
onClick {
activity?.let { activity ->
MaterialAlertDialogBuilder(activity)
.setTitle(R.string.favorites_sync_reset)
.setMessage(R.string.favorites_sync_reset_message)
.setPositiveButton(android.R.string.ok) { _, _ ->
launchIO {
deleteFavoriteEntries.await()
withUIContext {
activity.toast(context.getString(R.string.sync_state_reset), Toast.LENGTH_LONG)
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.setCancelable(false)
.show()
}
}
}
}
preferenceCategory {
titleRes = R.string.gallery_update_checker
intListPreference {
bindTo(unsortedPreferences.exhAutoUpdateFrequency())
titleRes = R.string.time_between_batches
entriesRes = arrayOf(
R.string.time_between_batches_never,
R.string.time_between_batches_1_hour,
R.string.time_between_batches_2_hours,
R.string.time_between_batches_3_hours,
R.string.time_between_batches_6_hours,
R.string.time_between_batches_12_hours,
R.string.time_between_batches_24_hours,
R.string.time_between_batches_48_hours,
)
entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48")
unsortedPreferences.exhAutoUpdateFrequency().changes()
.onEach { newVal ->
summary = if (newVal == 0) {
context.getString(R.string.time_between_batches_summary_1, context.getString(R.string.app_name))
} else {
context.getString(R.string.time_between_batches_summary_2, context.getString(R.string.app_name), newVal, EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION)
}
}
.launchIn(viewScope)
onChange { newValue ->
val interval = (newValue as String).toInt()
EHentaiUpdateWorker.scheduleBackground(context, interval)
true
}
}
multiSelectListPreference {
bindTo(unsortedPreferences.exhAutoUpdateRequirements())
titleRes = R.string.auto_update_restrictions
entriesRes = arrayOf(R.string.connected_to_wifi, R.string.charging)
entryValues = arrayOf(DEVICE_ONLY_ON_WIFI, DEVICE_CHARGING)
fun updateSummary() {
val restrictions = unsortedPreferences.exhAutoUpdateRequirements().get()
.sorted()
.map {
when (it) {
DEVICE_ONLY_ON_WIFI -> context.getString(R.string.connected_to_wifi)
DEVICE_CHARGING -> context.getString(R.string.charging)
else -> it
}
}
val restrictionsText = if (restrictions.isEmpty()) {
context.getString(R.string.none)
} else {
restrictions.joinToString()
}
summary = context.getString(R.string.restrictions, restrictionsText)
}
visibleIf(unsortedPreferences.exhAutoUpdateFrequency()) { it > 0 }
onChange {
// Post to event looper to allow the preference to be updated.
ContextCompat.getMainExecutor(context).execute { EHentaiUpdateWorker.scheduleBackground(context) }
true
}
unsortedPreferences.exhAutoUpdateRequirements().changes()
.onEach { updateSummary() }
.launchIn(viewScope)
}
preference {
key = "pref_show_updater_statistics"
titleRes = R.string.show_updater_statistics
onClick {
val progress = MaterialAlertDialogBuilder(context)
.setMessage(R.string.gallery_updater_statistics_collection)
.setCancelable(false)
.create()
progress.show()
viewScope.launch(Dispatchers.IO) {
val updateInfo = try {
val stats =
unsortedPreferences.exhAutoUpdateStats().get().nullIfBlank()?.let {
Json.decodeFromString<EHentaiUpdaterStats>(it)
}
val statsText = if (stats != null) {
context.getString(R.string.gallery_updater_stats_text, getRelativeTimeString(getRelativeTimeFromNow(stats.startTime.milliseconds), context), stats.updateCount, stats.possibleUpdates)
} else {
context.getString(R.string.gallery_updater_not_ran_yet)
}
val allMeta = getExhFavoriteMangaWithMetadata.await()
.mapNotNull {
getFlatMetadataById.await(it.id)
?.raise<EHentaiSearchMetadata>()
}
fun metaInRelativeDuration(duration: Duration): Int {
val durationMs = duration.inWholeMilliseconds
return allMeta.asSequence().filter {
System.currentTimeMillis() - it.lastUpdateCheck < durationMs
}.count()
}
statsText + "\n\n" + context.getString(
R.string.gallery_updater_stats_time,
metaInRelativeDuration(1.hours),
metaInRelativeDuration(6.hours),
metaInRelativeDuration(12.hours),
metaInRelativeDuration(1.days),
metaInRelativeDuration(2.days),
metaInRelativeDuration(7.days),
metaInRelativeDuration(30.days),
metaInRelativeDuration(365.days),
)
} finally {
progress.dismiss()
}
withContext(Dispatchers.Main) {
MaterialAlertDialogBuilder(context)
.setTitle(R.string.gallery_updater_statistics)
.setMessage(updateInfo)
.setPositiveButton(android.R.string.ok, null)
.show()
}
}
}
}
}
}
private fun getRelativeTimeFromNow(then: Duration): RelativeTime {
val now = System.currentTimeMillis().milliseconds
var period: Duration = now - then
val relativeTime = RelativeTime()
while (period > 0.milliseconds) {
when {
period >= 365.days -> {
(period.inWholeDays / 365).let {
relativeTime.years = it
period -= (it * 365).days
}
continue
}
period >= 30.days -> {
(period.inWholeDays / 30).let {
relativeTime.months = it
period -= (it * 30).days
}
}
period >= 7.days -> {
(period.inWholeDays / 7).let {
relativeTime.weeks = it
period -= (it * 7).days
}
}
period >= 1.days -> {
period.inWholeDays.let {
relativeTime.days = it
period -= it.days
}
}
period >= 1.hours -> {
period.inWholeHours.let {
relativeTime.hours = it
period -= it.hours
}
}
period >= 1.minutes -> {
period.inWholeMinutes.let {
relativeTime.minutes = it
period -= it.minutes
}
}
period >= 1.seconds -> {
period.inWholeSeconds.let {
relativeTime.seconds = it
period -= it.seconds
}
}
period >= 1.milliseconds -> {
period.inWholeMilliseconds.let {
relativeTime.milliseconds = it
}
period = Duration.ZERO
}
}
}
return relativeTime
}
private fun getRelativeTimeString(relativeTime: RelativeTime, context: Context): String {
return relativeTime.years?.let { context.resources.getQuantityString(R.plurals.humanize_year, it.toInt(), it) }
?: relativeTime.months?.let { context.resources.getQuantityString(R.plurals.humanize_month, it.toInt(), it) }
?: relativeTime.weeks?.let { context.resources.getQuantityString(R.plurals.humanize_week, it.toInt(), it) }
?: relativeTime.days?.let { context.resources.getQuantityString(R.plurals.humanize_day, it.toInt(), it) }
?: relativeTime.hours?.let { context.resources.getQuantityString(R.plurals.humanize_hour, it.toInt(), it) }
?: relativeTime.minutes?.let { context.resources.getQuantityString(R.plurals.humanize_minute, it.toInt(), it) }
?: relativeTime.seconds?.let { context.resources.getQuantityString(R.plurals.humanize_second, it.toInt(), it) }
?: context.getString(R.string.humanize_fallback)
}
data class RelativeTime(
var years: Long? = null,
var months: Long? = null,
var weeks: Long? = null,
var days: Long? = null,
var hours: Long? = null,
var minutes: Long? = null,
var seconds: Long? = null,
var milliseconds: Long? = null,
)
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == LOGIN_RESULT) {
// Upload settings
WarnConfigureDialogController.uploadSettings(router)
}
}
}
companion object {
const val LOGIN_RESULT = 500
}
}

View File

@ -1,129 +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.UnsortedPreferences
import eu.kanade.domain.library.service.LibraryPreferences
import eu.kanade.domain.ui.UiPreferences
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.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.LocaleHelper
import org.xmlpull.v1.XmlPullParser
import uy.kohesive.injekt.injectLazy
class SettingsGeneralController : SettingsController() {
private val libraryPreferences: LibraryPreferences by injectLazy()
// SY -->
private val uiPreferences: UiPreferences by injectLazy()
private val unsortedPreferences: UnsortedPreferences by injectLazy()
// SY <--
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
}
}
// --> EXH
preferenceCategory {
titleRes = R.string.pref_category_fork
switchPreference {
bindTo(uiPreferences.expandFilters())
titleRes = R.string.toggle_expand_search_filters
}
switchPreference {
bindTo(unsortedPreferences.autoSolveCaptcha())
titleRes = R.string.auto_solve_captchas
summaryRes = R.string.auto_solve_captchas_summary
}
switchPreference {
bindTo(uiPreferences.recommendsInOverflow())
titleRes = R.string.put_recommends_in_overflow
summaryRes = R.string.put_recommends_in_overflow_summary
}
switchPreference {
bindTo(uiPreferences.mergeInOverflow())
titleRes = R.string.put_merge_in_overflow
summaryRes = R.string.put_merge_in_overflow_summary
}
}
// <-- EXH
}
}

View File

@ -1,441 +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.UnsortedPreferences
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.model.GroupLibraryMode
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.ui.category.genre.SortTagController
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.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()
// SY -->
private val unsortedPreferences: UnsortedPreferences by injectLazy()
// SY <--
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)
}
// SY -->
listPreference {
bindTo(libraryPreferences.groupLibraryUpdateType())
titleRes = R.string.library_group_updates
entriesRes = arrayOf(
R.string.library_group_updates_global,
R.string.library_group_updates_all_but_ungrouped,
R.string.library_group_updates_all,
)
entryValues = arrayOf(
GroupLibraryMode.GLOBAL.name,
GroupLibraryMode.ALL_BUT_UNGROUPED.name,
GroupLibraryMode.ALL.name,
)
summary = "%s"
}
// SY <--
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
}
}
}
// SY -->
preferenceCategory {
titleRes = R.string.pref_sorting_settings
preference {
key = "pref_tag_sorting"
titleRes = R.string.pref_tag_sorting
val count = libraryPreferences.sortTagsForLibrary().get().size
summary = context.resources.getQuantityString(R.plurals.pref_tag_sorting_desc, count, count)
onClick {
router.pushController(SortTagController())
}
}
}
if (unsortedPreferences.skipPreMigration().get() || unsortedPreferences.migrationSources().get()
.isNotEmpty()
) {
preferenceCategory {
titleRes = R.string.migration
switchPreference {
bindTo(unsortedPreferences.skipPreMigration())
titleRes = R.string.skip_pre_migration
summaryRes = R.string.pref_skip_pre_migration_summary
}
}
}
// SY <--
}
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()
}
}
}

View File

@ -1,124 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.preference.bindTo
import eu.kanade.tachiyomi.util.preference.listPreference
import eu.kanade.tachiyomi.util.preference.onClick
import eu.kanade.tachiyomi.util.preference.preference
import eu.kanade.tachiyomi.util.preference.summaryRes
import eu.kanade.tachiyomi.util.preference.titleRes
import exh.md.utils.MdUtil
import exh.widget.preference.MangaDexLoginPreference
import exh.widget.preference.MangadexLoginDialog
import exh.widget.preference.MangadexLogoutDialog
import uy.kohesive.injekt.injectLazy
class SettingsMangaDexController :
SettingsController(),
MangadexLoginDialog.Listener,
MangadexLogoutDialog.Listener {
private val sourcePreferences: SourcePreferences by injectLazy()
private val unsortedPreferences: UnsortedPreferences by injectLazy()
private val mdex by lazy { MdUtil.getEnabledMangaDex(unsortedPreferences, sourcePreferences) }
override fun setupPreferenceScreen(screen: PreferenceScreen) = screen.apply {
titleRes = R.string.mangadex_specific_settings
val mdex = mdex ?: return@apply
val sourcePreference = MangaDexLoginPreference(context, mdex).apply {
title = mdex.name + " Login"
key = getSourceKey(source.id)
setOnLoginClickListener {
if (mdex.isLogged()) {
val dialog = MangadexLogoutDialog(source)
dialog.targetController = this@SettingsMangaDexController
dialog.showDialog(router)
} else {
val dialog = MangadexLoginDialog(source)
dialog.targetController = this@SettingsMangaDexController
dialog.showDialog(router)
}
}
}
addPreference(sourcePreference)
listPreference {
bindTo(unsortedPreferences.preferredMangaDexId())
titleRes = R.string.mangadex_preffered_source
summaryRes = R.string.mangadex_preffered_source_summary
val mangaDexs = MdUtil.getEnabledMangaDexs(sourcePreferences)
entries = mangaDexs.map { it.toString() }.toTypedArray()
entryValues = mangaDexs.map { it.id.toString() }.toTypedArray()
}
preference {
key = "pref_sync_mangadex_into_this"
titleRes = R.string.mangadex_sync_follows_to_library
summaryRes = R.string.mangadex_sync_follows_to_library_summary
onClick {
val items = context.resources.getStringArray(R.array.md_follows_options)
.drop(1)
.toTypedArray()
val selection = items.mapIndexed { index, _ ->
index == 0 || index == 5
}.toBooleanArray()
MaterialAlertDialogBuilder(context)
.setTitle(R.string.mangadex_sync_follows_to_library)
// .setMessage(R.string.mangadex_sync_follows_to_library_message)
.setMultiChoiceItems(
items,
selection,
) { _, which, selected ->
selection[which] = selected
}
.setPositiveButton(android.R.string.ok) { _, _ ->
unsortedPreferences.mangadexSyncToLibraryIndexes().set(
items.filterIndexed { index, _ -> selection[index] }
.mapIndexed { index, _ -> (index + 1).toString() }
.toSet(),
)
LibraryUpdateService.start(
context,
target = LibraryUpdateService.Target.SYNC_FOLLOWS,
)
}
.setNegativeButton(android.R.string.cancel, null)
.show()
}
}
preference {
titleRes = R.string.mangadex_push_favorites_to_mangadex
summaryRes = R.string.mangadex_push_favorites_to_mangadex_summary
onClick {
LibraryUpdateService.start(
context,
target = LibraryUpdateService.Target.PUSH_FAVORITES,
)
}
}
}
override fun siteLoginDialogClosed(source: Source) {
val pref = findPreference(getSourceKey(source.id)) as? MangaDexLoginPreference
pref?.notifyChanged()
}
override fun siteLogoutDialogClosed(source: Source) {
val pref = findPreference(getSourceKey(source.id)) as? MangaDexLoginPreference
pref?.notifyChanged()
}
private fun getSourceKey(sourceId: Long): String {
return "source_$sourceId"
}
}

View File

@ -1,563 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder
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.base.controller.DialogController
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
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.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.hasDisplayCutout
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
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
}
// SY -->
switchPreference {
bindTo(readerPreferences.forceHorizontalSeekbar())
titleRes = R.string.pref_force_horz_seekbar
summaryRes = R.string.pref_force_horz_seekbar_summary
}
switchPreference {
bindTo(readerPreferences.landscapeVerticalSeekbar())
titleRes = R.string.pref_show_vert_seekbar_landscape
summaryRes = R.string.pref_show_vert_seekbar_landscape_summary
visibleIf(readerPreferences.forceHorizontalSeekbar()) { !it }
}
switchPreference {
bindTo(readerPreferences.leftVerticalSeekbar())
titleRes = R.string.pref_left_handed_vertical_seekbar
summaryRes = R.string.pref_left_handed_vertical_seekbar_summary
visibleIf(readerPreferences.forceHorizontalSeekbar()) { !it }
}
// SY <--
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
}
}
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
}
// SY -->
switchPreference {
bindTo(readerPreferences.pageTransitionsPager())
titleRes = R.string.pref_page_transitions
}
// SY <--
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
}
// SY -->
switchPreference {
bindTo(readerPreferences.pageTransitionsWebtoon())
titleRes = R.string.pref_page_transitions
}
switchPreference {
bindTo(readerPreferences.webtoonEnableZoomOut())
titleRes = R.string.enable_zoom_out
}
// SY <--
}
// SY -->
preferenceCategory {
titleRes = R.string.vertical_plus_viewer
switchPreference {
bindTo(readerPreferences.continuousVerticalTappingByPage())
titleRes = R.string.tap_scroll_page
summaryRes = R.string.tap_scroll_page_summary
}
switchPreference {
bindTo(readerPreferences.cropBordersContinuousVertical())
titleRes = R.string.pref_crop_borders
}
}
// SY <--
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
}
}
// SY -->
preferenceCategory {
titleRes = R.string.page_downloading
intListPreference {
bindTo(readerPreferences.preloadSize())
titleRes = R.string.reader_preload_amount
entryValues = arrayOf(
"4",
"6",
"8",
"10",
"12",
"14",
"16",
"20",
)
entriesRes = arrayOf(
R.string.reader_preload_amount_4_pages,
R.string.reader_preload_amount_6_pages,
R.string.reader_preload_amount_8_pages,
R.string.reader_preload_amount_10_pages,
R.string.reader_preload_amount_12_pages,
R.string.reader_preload_amount_14_pages,
R.string.reader_preload_amount_16_pages,
R.string.reader_preload_amount_20_pages,
)
summaryRes = R.string.reader_preload_amount_summary
}
intListPreference {
bindTo(readerPreferences.readerThreads())
titleRes = R.string.download_threads
entries = arrayOf("1", "2", "3", "4", "5")
entryValues = entries
summaryRes = R.string.download_threads_summary
}
listPreference {
bindTo(readerPreferences.cacheSize())
titleRes = R.string.reader_cache_size
entryValues = arrayOf(
"50",
"75",
"100",
"150",
"250",
"500",
"750",
"1000",
"1500",
"2000",
"2500",
"3000",
"3500",
"4000",
"4500",
"5000",
)
entries = arrayOf(
"50 MB",
"75 MB",
"100 MB",
"150 MB",
"250 MB",
"500 MB",
"750 MB",
"1 GB",
"1.5 GB",
"2 GB",
"2.5 GB",
"3 GB",
"3.5 GB",
"4 GB",
"4.5 GB",
"5 GB",
)
summaryRes = R.string.reader_cache_size_summary
}
switchPreference {
bindTo(readerPreferences.aggressivePageLoading())
titleRes = R.string.aggressively_load_pages
summaryRes = R.string.aggressively_load_pages_summary
}
}
preferenceCategory {
titleRes = R.string.pref_category_fork
switchPreference {
bindTo(readerPreferences.readerInstantRetry())
titleRes = R.string.skip_queue_on_retry
summaryRes = R.string.skip_queue_on_retry_summary
}
switchPreference {
bindTo(readerPreferences.preserveReadingPosition())
titleRes = R.string.preserve_reading_position
}
switchPreference {
bindTo(readerPreferences.useAutoWebtoon())
titleRes = R.string.auto_webtoon_mode
summaryRes = R.string.auto_webtoon_mode_summary
}
preference {
key = "reader_bottom_buttons_pref"
titleRes = R.string.reader_bottom_buttons
summaryRes = R.string.reader_bottom_buttons_summary
onClick {
ReaderBottomButtonsDialog().showDialog(router)
}
}
intListPreference {
bindTo(readerPreferences.pageLayout())
titleRes = R.string.page_layout
summaryRes = R.string.automatic_can_still_switch
entriesRes = arrayOf(
R.string.single_page,
R.string.double_pages,
R.string.automatic_orientation,
)
entryValues = arrayOf("0", "1", "2")
}
switchPreference {
bindTo(readerPreferences.invertDoublePages())
titleRes = R.string.invert_double_pages
visibleIf(readerPreferences.pageLayout()) { it != PagerConfig.PageLayout.SINGLE_PAGE }
}
intListPreference {
bindTo(readerPreferences.centerMarginType())
titleRes = R.string.center_margin
entriesRes = arrayOf(
R.string.center_margin_none,
R.string.center_margin_double_page,
R.string.center_margin_wide_page,
R.string.center_margin_double_and_wide_page,
)
entryValues = arrayOf("0", "1", "2", "3")
}
}
// SY <--
}
// SY -->
class ReaderBottomButtonsDialog : DialogController() {
private val readerPreferences: ReaderPreferences = Injekt.get()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val oldSelection = readerPreferences.readerBottomButtons().get()
val values = ReaderBottomButton.values()
val selection = values.map { it.value in oldSelection }
.toBooleanArray()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.reader_bottom_buttons)
.setMultiChoiceItems(
values.map { activity!!.getString(it.stringRes) }.toTypedArray(),
selection,
) { _, which, selected ->
selection[which] = selected
}
.setPositiveButton(android.R.string.ok) { _, _ ->
val included = values
.filterIndexed { index, _ ->
selection[index]
}
.map { it.value }
.toSet()
readerPreferences.readerBottomButtons().set(included)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}
// SY <--
}

View File

@ -1,203 +0,0 @@
package eu.kanade.tachiyomi.ui.setting
import android.app.Dialog
import android.os.Bundle
import androidx.biometric.BiometricPrompt
import androidx.fragment.app.FragmentActivity
import androidx.preference.Preference
import androidx.preference.PreferenceScreen
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.core.security.SecurityPreferences
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.base.controller.pushController
import eu.kanade.tachiyomi.ui.base.delegate.SecureActivityDelegate
import eu.kanade.tachiyomi.ui.category.biometric.BiometricTimesController
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.onClick
import eu.kanade.tachiyomi.util.preference.preference
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
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()
}
// SY -->
preference {
key = "pref_edit_lock_times"
titleRes = R.string.action_edit_biometric_lock_times
val timeRanges = securityPreferences.authenticatorTimeRanges().get().size
summary = context.resources.getQuantityString(R.plurals.num_lock_times, timeRanges, timeRanges)
visibleIf(securityPreferences.useAuthenticator()) { it }
onClick {
router.pushController(BiometricTimesController())
}
}
preference {
key = "pref_edit_lock_days"
titleRes = R.string.biometric_lock_days
summaryRes = R.string.biometric_lock_days_summary
visibleIf(securityPreferences.useAuthenticator()) { it }
onClick {
SetLockedDaysDialog().showDialog(router)
}
}
// SY <--
infoPreference(R.string.secure_screen_summary)
}
// SY -->
class SetLockedDaysDialog(bundle: Bundle? = null) : DialogController(bundle) {
val securityPreferences: SecurityPreferences by injectLazy()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
val activity = activity!!
val options = listOf(
R.string.sunday,
R.string.monday,
R.string.tuesday,
R.string.wednesday,
R.string.thursday,
R.string.friday,
R.string.saturday,
)
.map { activity.getString(it) }
.toTypedArray()
val lockDays = securityPreferences.authenticatorDays().get()
val selection = BooleanArray(7) {
when (it) {
0 -> (lockDays and SecureActivityDelegate.LOCK_SUNDAY) == SecureActivityDelegate.LOCK_SUNDAY
1 -> (lockDays and SecureActivityDelegate.LOCK_MONDAY) == SecureActivityDelegate.LOCK_MONDAY
2 -> (lockDays and SecureActivityDelegate.LOCK_TUESDAY) == SecureActivityDelegate.LOCK_TUESDAY
3 -> (lockDays and SecureActivityDelegate.LOCK_WEDNESDAY) == SecureActivityDelegate.LOCK_WEDNESDAY
4 -> (lockDays and SecureActivityDelegate.LOCK_THURSDAY) == SecureActivityDelegate.LOCK_THURSDAY
5 -> (lockDays and SecureActivityDelegate.LOCK_FRIDAY) == SecureActivityDelegate.LOCK_FRIDAY
6 -> (lockDays and SecureActivityDelegate.LOCK_SATURDAY) == SecureActivityDelegate.LOCK_SATURDAY
else -> false
}
}
return MaterialAlertDialogBuilder(activity)
.setTitle(R.string.biometric_lock_days)
.setMultiChoiceItems(
options,
selection,
) { _, which, selected ->
selection[which] = selected
}
.setPositiveButton(android.R.string.ok) { _, _ ->
var flags = 0
selection.forEachIndexed { index, checked ->
if (checked) {
when (index) {
0 -> flags = flags or SecureActivityDelegate.LOCK_SUNDAY
1 -> flags = flags or SecureActivityDelegate.LOCK_MONDAY
2 -> flags = flags or SecureActivityDelegate.LOCK_TUESDAY
3 -> flags = flags or SecureActivityDelegate.LOCK_WEDNESDAY
4 -> flags = flags or SecureActivityDelegate.LOCK_THURSDAY
5 -> flags = flags or SecureActivityDelegate.LOCK_FRIDAY
6 -> flags = flags or SecureActivityDelegate.LOCK_SATURDAY
}
}
}
securityPreferences.authenticatorDays().set(flags)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
}
// SY <--
}

View File

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

View File

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

View File

@ -1,68 +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>, /* SY --> */ keepReadManga: Boolean /* SY <-- */) {
// SY -->
if (keepReadManga) {
database.mangasQueries.deleteMangasNotInLibraryAndNotReadBySourceIds(sourceIds)
} else {
database.mangasQueries.deleteMangasNotInLibraryBySourceIds(sourceIds)
}
// SY <--
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()
}
}

View File

@ -1,93 +0,0 @@
package eu.kanade.tachiyomi.ui.setting.eh
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.ScrollView
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.EhDialogCategoriesBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.setting.SettingsEhController
import uy.kohesive.injekt.injectLazy
class FrontPageCategoriesDialog(
bundle: Bundle? = null,
) : DialogController(bundle) {
var binding: EhDialogCategoriesBinding? = null
private set
val preferences: UnsortedPreferences by injectLazy()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = EhDialogCategoriesBinding.inflate(LayoutInflater.from(activity!!))
val view = ScrollView(binding!!.root.context).apply {
addView(binding!!.root)
}
onViewCreated()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.language_filtering)
.setMessage(R.string.language_filtering_summary)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
onPositive()
}
.setOnDismissListener {
onPositive()
}
.setOnCancelListener {
onPositive()
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
fun onViewCreated() {
with(binding!!) {
val list = preferences.exhEnabledCategories().get().split(",").map { !it.toBoolean() }
doujinshiCheckbox.isChecked = list[0]
mangaCheckbox.isChecked = list[1]
artistCgCheckbox.isChecked = list[2]
gameCgCheckbox.isChecked = list[3]
westernCheckbox.isChecked = list[4]
nonHCheckbox.isChecked = list[5]
imageSetCheckbox.isChecked = list[6]
cosplayCheckbox.isChecked = list[7]
asianPornCheckbox.isChecked = list[8]
miscCheckbox.isChecked = list[9]
}
}
fun onPositive() {
with(binding!!) {
preferences.exhEnabledCategories().set(
listOf(
(!doujinshiCheckbox.isChecked),
(!mangaCheckbox.isChecked),
(!artistCgCheckbox.isChecked),
(!gameCgCheckbox.isChecked),
(!westernCheckbox.isChecked),
(!nonHCheckbox.isChecked),
(!imageSetCheckbox.isChecked),
(!cosplayCheckbox.isChecked),
(!asianPornCheckbox.isChecked),
(!miscCheckbox.isChecked),
).joinToString(separator = ",") { it.toString() },
)
}
with(targetController as? SettingsEhController ?: return) {
unsortedPreferences.exhSettingsLanguages().reconfigure()
}
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (!type.isEnter) {
binding = null
}
}
}

View File

@ -1,183 +0,0 @@
package eu.kanade.tachiyomi.ui.setting.eh
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import android.widget.ScrollView
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.UnsortedPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.EhDialogLanguagesBinding
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.ui.setting.SettingsEhController
import uy.kohesive.injekt.injectLazy
class LanguagesDialog(
bundle: Bundle? = null,
) : DialogController(bundle) {
var binding: EhDialogLanguagesBinding? = null
private set
val preferences: UnsortedPreferences by injectLazy()
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = EhDialogLanguagesBinding.inflate(LayoutInflater.from(activity!!))
val view = ScrollView(binding!!.root.context).apply {
addView(binding!!.root)
}
onViewCreated()
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.language_filtering)
.setMessage(R.string.language_filtering_summary)
.setView(view)
.setPositiveButton(android.R.string.ok) { _, _ ->
onPositive()
}
.setOnDismissListener {
onPositive()
}
.setOnCancelListener {
onPositive()
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
fun onViewCreated() {
val settingsLanguages = preferences.exhSettingsLanguages().get().split("\n")
val japanese = settingsLanguages[0].split("*").map { it.toBoolean() }
val english = settingsLanguages[1].split("*").map { it.toBoolean() }
val chinese = settingsLanguages[2].split("*").map { it.toBoolean() }
val dutch = settingsLanguages[3].split("*").map { it.toBoolean() }
val french = settingsLanguages[4].split("*").map { it.toBoolean() }
val german = settingsLanguages[5].split("*").map { it.toBoolean() }
val hungarian = settingsLanguages[6].split("*").map { it.toBoolean() }
val italian = settingsLanguages[7].split("*").map { it.toBoolean() }
val korean = settingsLanguages[8].split("*").map { it.toBoolean() }
val polish = settingsLanguages[9].split("*").map { it.toBoolean() }
val portuguese = settingsLanguages[10].split("*").map { it.toBoolean() }
val russian = settingsLanguages[11].split("*").map { it.toBoolean() }
val spanish = settingsLanguages[12].split("*").map { it.toBoolean() }
val thai = settingsLanguages[13].split("*").map { it.toBoolean() }
val vietnamese = settingsLanguages[14].split("*").map { it.toBoolean() }
val notAvailable =
settingsLanguages[15].split("*").map { it.toBoolean() }
val other = settingsLanguages[16].split("*").map { it.toBoolean() }
with(binding!!) {
japaneseOriginal.isChecked = japanese[0]
japaneseTranslated.isChecked = japanese[1]
japaneseRewrite.isChecked = japanese[2]
japaneseOriginal.isChecked = japanese[0]
japaneseTranslated.isChecked = japanese[1]
japaneseRewrite.isChecked = japanese[2]
englishOriginal.isChecked = english[0]
englishTranslated.isChecked = english[1]
englishRewrite.isChecked = english[2]
chineseOriginal.isChecked = chinese[0]
chineseTranslated.isChecked = chinese[1]
chineseRewrite.isChecked = chinese[2]
dutchOriginal.isChecked = dutch[0]
dutchTranslated.isChecked = dutch[1]
dutchRewrite.isChecked = dutch[2]
frenchOriginal.isChecked = french[0]
frenchTranslated.isChecked = french[1]
frenchRewrite.isChecked = french[2]
germanOriginal.isChecked = german[0]
germanTranslated.isChecked = german[1]
germanRewrite.isChecked = german[2]
hungarianOriginal.isChecked = hungarian[0]
hungarianTranslated.isChecked = hungarian[1]
hungarianRewrite.isChecked = hungarian[2]
italianOriginal.isChecked = italian[0]
italianTranslated.isChecked = italian[1]
italianRewrite.isChecked = italian[2]
koreanOriginal.isChecked = korean[0]
koreanTranslated.isChecked = korean[1]
koreanRewrite.isChecked = korean[2]
polishOriginal.isChecked = polish[0]
polishTranslated.isChecked = polish[1]
polishRewrite.isChecked = polish[2]
portugueseOriginal.isChecked = portuguese[0]
portugueseTranslated.isChecked = portuguese[1]
portugueseRewrite.isChecked = portuguese[2]
russianOriginal.isChecked = russian[0]
russianTranslated.isChecked = russian[1]
russianRewrite.isChecked = russian[2]
spanishOriginal.isChecked = spanish[0]
spanishTranslated.isChecked = spanish[1]
spanishRewrite.isChecked = spanish[2]
thaiOriginal.isChecked = thai[0]
thaiTranslated.isChecked = thai[1]
thaiRewrite.isChecked = thai[2]
vietnameseOriginal.isChecked = vietnamese[0]
vietnameseTranslated.isChecked = vietnamese[1]
vietnameseRewrite.isChecked = vietnamese[2]
notAvailableOriginal.isChecked = notAvailable[0]
notAvailableTranslated.isChecked = notAvailable[1]
notAvailableRewrite.isChecked = notAvailable[2]
otherOriginal.isChecked = other[0]
otherTranslated.isChecked = other[1]
otherRewrite.isChecked = other[2]
}
}
fun onPositive() {
val languages = with(binding!!) {
listOf(
"${japaneseOriginal.isChecked}*${japaneseTranslated.isChecked}*${japaneseRewrite.isChecked}",
"${englishOriginal.isChecked}*${englishTranslated.isChecked}*${englishRewrite.isChecked}",
"${chineseOriginal.isChecked}*${chineseTranslated.isChecked}*${chineseRewrite.isChecked}",
"${dutchOriginal.isChecked}*${dutchTranslated.isChecked}*${dutchRewrite.isChecked}",
"${frenchOriginal.isChecked}*${frenchTranslated.isChecked}*${frenchRewrite.isChecked}",
"${germanOriginal.isChecked}*${germanTranslated.isChecked}*${germanRewrite.isChecked}",
"${hungarianOriginal.isChecked}*${hungarianTranslated.isChecked}*${hungarianRewrite.isChecked}",
"${italianOriginal.isChecked}*${italianTranslated.isChecked}*${italianRewrite.isChecked}",
"${koreanOriginal.isChecked}*${koreanTranslated.isChecked}*${koreanRewrite.isChecked}",
"${polishOriginal.isChecked}*${polishTranslated.isChecked}*${polishRewrite.isChecked}",
"${portugueseOriginal.isChecked}*${portugueseTranslated.isChecked}*${portugueseRewrite.isChecked}",
"${russianOriginal.isChecked}*${russianTranslated.isChecked}*${russianRewrite.isChecked}",
"${spanishOriginal.isChecked}*${spanishTranslated.isChecked}*${spanishRewrite.isChecked}",
"${thaiOriginal.isChecked}*${thaiTranslated.isChecked}*${thaiRewrite.isChecked}",
"${vietnameseOriginal.isChecked}*${vietnameseTranslated.isChecked}*${vietnameseRewrite.isChecked}",
"${notAvailableOriginal.isChecked}*${notAvailableTranslated.isChecked}*${notAvailableRewrite.isChecked}",
"${otherOriginal.isChecked}*${otherTranslated.isChecked}*${otherRewrite.isChecked}",
).joinToString("\n")
}
preferences.exhSettingsLanguages().set(languages)
with(targetController as? SettingsEhController ?: return) {
unsortedPreferences.exhSettingsLanguages().reconfigure()
}
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (!type.isEnter) {
binding = null
}
}
}

View File

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

View File

@ -1,159 +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.domain.UnsortedPreferences
import eu.kanade.domain.source.service.SourcePreferences
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.SettingsEhController
import eu.kanade.tachiyomi.ui.setting.SettingsGeneralController
import eu.kanade.tachiyomi.ui.setting.SettingsLibraryController
import eu.kanade.tachiyomi.ui.setting.SettingsMangaDexController
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 exh.md.utils.MdUtil
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
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.
*/
// SY -->
private val settingControllersList: List<KClass<out SettingsController>> = run {
val controllers = mutableListOf(
SettingsAdvancedController::class,
SettingsAppearanceController::class,
SettingsBackupController::class,
SettingsBrowseController::class,
SettingsDownloadController::class,
SettingsGeneralController::class,
SettingsLibraryController::class,
SettingsReaderController::class,
SettingsSecurityController::class,
SettingsTrackingController::class,
)
val preferences = Injekt.get<UnsortedPreferences>()
val sourcePreferences = Injekt.get<SourcePreferences>()
if (MdUtil.getEnabledMangaDexs(sourcePreferences).isNotEmpty()) {
controllers += SettingsMangaDexController::class
}
if (preferences.isHentaiEnabled().get()) {
controllers += SettingsEhController::class
}
controllers
}
// SY <--
/**
* 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,
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -3,17 +3,10 @@ package eu.kanade.tachiyomi.widget.materialdialogs
import android.view.LayoutInflater
import android.view.inputmethod.InputMethodManager
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.appcompat.app.AlertDialog
import androidx.core.content.getSystemService
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.tachiyomi.databinding.DialogStubQuadstatemultichoiceBinding
import eu.kanade.tachiyomi.databinding.DialogStubTextinputBinding
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.resume
fun MaterialAlertDialogBuilder.setTextInput(
hint: String? = null,
@ -34,59 +27,3 @@ fun MaterialAlertDialogBuilder.setTextInput(
}
return setView(binding.root)
}
/**
* 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() }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,68 +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()
}
/* SY --> */
open /* SY <-- */ 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)
}

View File

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

View File

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

View File

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

View File

@ -1,57 +0,0 @@
package exh.widget.preference
import android.content.Context
import android.util.AttributeSet
import androidx.core.view.isVisible
import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.PrefItemMangadexBinding
import eu.kanade.tachiyomi.source.online.all.MangaDex
import eu.kanade.tachiyomi.util.system.getResourceColor
class MangaDexLoginPreference @JvmOverloads constructor(
context: Context,
val source: MangaDex,
attrs: AttributeSet? = null,
) : Preference(context, attrs) {
init {
layoutResource = R.layout.pref_item_mangadex
}
var binding: PrefItemMangadexBinding? = null
private var onLoginClick: () -> Unit = {}
override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder)
binding = PrefItemMangadexBinding.bind(holder.itemView)
holder.itemView.setOnClickListener {
onLoginClick()
}
val loginFrame = binding?.loginFrame
val color = if (source.isLogged()) {
context.getResourceColor(R.attr.colorAccent)
} else {
context.getResourceColor(R.attr.colorSecondary)
}
binding?.login?.setImageResource(R.drawable.ic_outline_people_alt_24dp)
binding?.login?.drawable?.setTint(color)
loginFrame?.isVisible = true
loginFrame?.setOnClickListener {
onLoginClick()
}
}
fun setOnLoginClickListener(block: () -> Unit) {
onLoginClick = block
}
// Make method public
public override fun notifyChanged() {
super.notifyChanged()
}
}

View File

@ -1,158 +0,0 @@
package exh.widget.preference
import android.app.Dialog
import android.os.Bundle
import android.view.LayoutInflater
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
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.UnsortedPreferences
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.PrefSiteLoginTwoFactorAuthBinding
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.toast
import exh.log.xLogW
import exh.source.getMainSource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangadexLoginDialog(bundle: Bundle? = null) : DialogController(bundle) {
val source = Injekt.get<SourceManager>().get(args.getLong("key", 0))?.getMainSource() as LoginSource
val preferences: UnsortedPreferences by injectLazy()
val scope = CoroutineScope(Job() + Dispatchers.Main)
lateinit var binding: PrefSiteLoginTwoFactorAuthBinding
constructor(source: LoginSource) : this(
bundleOf(
"key" to source.id,
),
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
binding = PrefSiteLoginTwoFactorAuthBinding.inflate(LayoutInflater.from(activity!!))
onViewCreated()
return MaterialAlertDialogBuilder(activity!!)
.setView(binding.root)
.create()
}
fun onViewCreated() {
binding.login.setMode(ActionProcessButton.Mode.ENDLESS)
binding.login.setOnClickListener { checkLogin() }
setCredentialsOnView()
when (source.twoFactorAuth) {
LoginSource.AuthSupport.REQUIRED -> {
binding.twoFactorCheck.isVisible = false
binding.twoFactorHolder.isVisible = true
}
LoginSource.AuthSupport.SUPPORTED -> {
binding.twoFactorCheck.setOnCheckedChangeListener { _, isChecked ->
binding.twoFactorHolder.isVisible = isChecked
}
}
LoginSource.AuthSupport.NOT_SUPPORTED -> {
binding.twoFactorCheck.isVisible = false
binding.twoFactorHolder.isVisible = false
}
}
}
private fun setCredentialsOnView() {
binding.username.setText(source.getUsername())
binding.password.setText(source.getPassword())
}
private fun checkLogin() {
val username = binding.username.text?.toString()
val password = binding.password.text?.toString()
val twoFactor = binding.twoFactorEdit.text?.toString()
if (username.isNullOrBlank() || password.isNullOrBlank() || (binding.twoFactorCheck.isChecked && twoFactor.isNullOrBlank())) {
errorResult()
launchUI {
binding.root.context.toast(R.string.fields_cannot_be_blank)
}
return
}
binding.login.progress = 1
dialog?.setCancelable(false)
dialog?.setCanceledOnTouchOutside(false)
scope.launch {
try {
val result = source.login(
username,
password,
twoFactor.toString(),
)
if (result) {
dialog?.dismiss()
withUIContext {
binding.root.context.toast(R.string.login_success)
}
} else {
errorResult()
}
} catch (error: Exception) {
errorResult()
xLogW("Login to Mangadex error", error)
error.message?.let { launchUI { binding.root.context.toast(it) } }
}
}
}
private fun errorResult() {
with(binding) {
dialog?.setCancelable(true)
dialog?.setCanceledOnTouchOutside(true)
login.progress = -1
login.setText(R.string.unknown_error)
}
}
override fun onChangeStarted(handler: ControllerChangeHandler, type: ControllerChangeType) {
super.onChangeStarted(handler, type)
if (!type.isEnter) {
onDialogClosed()
}
}
private fun onDialogClosed() {
scope.cancel()
if (activity != null) {
(activity as? Listener)?.siteLoginDialogClosed(source)
} else {
(targetController as? Listener)?.siteLoginDialogClosed(source)
}
}
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
interface Listener {
fun siteLoginDialogClosed(source: Source)
}
}

View File

@ -1,57 +0,0 @@
package exh.widget.preference
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.source.Source
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.ui.base.controller.DialogController
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.toast
import exh.source.getMainSource
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
class MangadexLogoutDialog(bundle: Bundle? = null) : DialogController(bundle) {
val source = Injekt.get<SourceManager>().get(args.getLong("key", 0))?.getMainSource() as LoginSource
val trackManager: TrackManager by injectLazy()
constructor(source: Source) : this(
bundleOf(
"key" to source.id,
),
)
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(activity!!)
.setTitle(R.string.logout)
.setPositiveButton(R.string.logout) { _, _ ->
launchIO {
if (source.logout()) {
withUIContext {
activity?.toast(R.string.logout_success)
(targetController as? Listener)?.siteLogoutDialogClosed(source)
}
} else {
withUIContext {
activity?.toast(R.string.unknown_error)
}
}
}
}
.setNegativeButton(android.R.string.cancel, null)
.create()
}
interface Listener {
fun siteLogoutDialogClosed(source: Source)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,238 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_column="0"
android:divider="?attr/colorOnBackground"
android:padding="16dp"
android:showDividers="middle">
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="Category" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="Enabled" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/doujinshi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Doujinshi" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/doujinshi_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/manga"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Manga" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/manga_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/artist_cg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Artist CG" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/artist_cg_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/game_cg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Game CG" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/game_cg_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/western"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Western" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/western_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/non_h"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Non-H" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/non_h_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/image_set"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Image Set" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/image_set_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/cosplay"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Cosplay" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/cosplay_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/asian_porn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Asian Porn" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/asian_porn_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/misc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Misc" />
<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/misc_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingHorizontal="16dp"
android:paddingVertical="16dp" />
</TableRow>
</TableLayout>

View File

@ -1,565 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_row="0"
android:layout_column="0"
android:padding="16dp" >
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4dp"
android:text="Language" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="Original" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp"
android:text="Translated" />
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:padding="8dp"
android:text="Rewrite" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/japanese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Japanese" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/japanese_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="invisible" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/japanese_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/japanese_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/english"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="English" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/english_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/english_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/english_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/chinese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Chinese" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/chinese_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/chinese_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/chinese_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/dutch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Dutch" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/dutch_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/dutch_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/dutch_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/french"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="French" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/french_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/french_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/french_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/german"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="German" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/german_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/german_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/german_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/hungarian"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Hungarian" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hungarian_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hungarian_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/hungarian_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/italian"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Italian" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/italian_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/italian_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/italian_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/korean"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Korean" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/korean_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/korean_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/korean_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/polish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Polish" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/polish_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/polish_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/polish_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/portuguese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Portuguese" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/portuguese_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/portuguese_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/portuguese_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/russian"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Russian" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/russian_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/russian_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/russian_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/spanish"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Spanish" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/spanish_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/spanish_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/spanish_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/thai"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Thai" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/thai_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/thai_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/thai_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/vietnamese"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Vietnamese" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/vietnamese_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/vietnamese_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/vietnamese_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/not_available"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="N/A" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/not_available_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/not_available_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/not_available_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
<TableRow
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/other"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:padding="4dp"
android:text="Other" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/other_original"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/other_translated"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/other_rewrite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</TableRow>
</TableLayout>

View File

@ -1,53 +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:autofillHints="username"
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:autofillHints="password"
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>

View File

@ -1,61 +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:background="?selectableItemBackground"
android:baselineAligned="false"
android:clipToPadding="false"
android:gravity="center_vertical"
android:minHeight="42dp"
android:paddingStart="?listPreferredItemPaddingStart"
android:paddingEnd="?listPreferredItemPaddingEnd"
tools:ignore="RtlHardcoded">
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:clipToPadding="false"
android:gravity="start|center_vertical"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"/>
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="?textAppearanceListItem"/>
<!-- Hidden view -->
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone" />
<LinearLayout
android:id="@+id/login_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginEnd="-16dp"
android:clipToPadding="false"
android:gravity="end|center_vertical"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/login" />
</LinearLayout>
</LinearLayout>

View File

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

View File

@ -1,86 +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="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/dialog_title"
style="@style/TextAppearance.MaterialComponents.Headline6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
tools:text="Log in to MangaDex" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/username_input"
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:autofillHints="username"
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:autofillHints="password"
android:inputType="textPassword" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/two_factor_holder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:hint="@string/two_factor"
android:visibility="gone"
tools:visibility="visible">
<eu.kanade.tachiyomi.widget.TachiyomiTextInputEditText
android:id="@+id/two_factor_edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.checkbox.MaterialCheckBox
android:id="@+id/two_factor_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="@string/two_factor" />
<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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,7 +56,6 @@ natural-comparator = "com.github.gpanther:java-nat-sort:natural-comparator-1.1"
markwon = "io.noties.markwon:core:4.6.2"
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-ui = "com.github.arkon.FlexibleAdapter:flexible-adapter-ui:c8013533"
photoview = "com.github.chrisbanes:PhotoView:2.3.0"