Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bf6aa3e8ae | ||
|
|
75fcfde892 | ||
|
|
89be27995e | ||
|
|
6738dc6cdc | ||
|
|
9ff0decb98 | ||
|
|
717d3717ba | ||
|
|
fdbca869a8 | ||
|
|
aafa750ca3 | ||
|
|
afe2bac672 | ||
|
|
b6f5d72db5 | ||
|
|
f0021a7601 | ||
|
|
ea5bfbb346 | ||
|
|
38fbcf4457 | ||
|
|
7c43e8f349 | ||
|
|
93df4e3dd9 | ||
|
|
f7d45a52a7 | ||
|
|
328f96d115 | ||
|
|
22338a72d8 | ||
|
|
256dedcb7c | ||
|
|
95d2438b84 | ||
|
|
f8d27e165e | ||
|
|
e2f024ae29 | ||
|
|
9c28fbeaf8 | ||
|
|
6458eb090a | ||
|
|
f1ca2b6958 | ||
|
|
60f031e7e3 | ||
|
|
e6a44ee0e1 | ||
|
|
7a3a641a07 | ||
|
|
1021efb239 | ||
|
|
edbe6509e1 | ||
|
|
44a6253bfc | ||
|
|
3d6e5df71e | ||
|
|
2733d2e58c | ||
|
|
0c78673870 | ||
|
|
d618948429 | ||
|
|
7584704d22 | ||
|
|
4a4658cc54 | ||
|
|
e9875f1a24 | ||
|
|
cc11257d80 | ||
|
|
4fd64601ef | ||
|
|
7eea490adf | ||
|
|
b2b12f7b93 | ||
|
|
66d026cffa | ||
|
|
5a15a20dd6 | ||
|
|
70450a9725 | ||
|
|
470a3c6a09 | ||
|
|
15f8b2f15d | ||
|
|
717cbe7630 | ||
|
|
d34075d29d | ||
|
|
80d4d66fb5 | ||
|
|
6b20b46761 | ||
|
|
cd764a4a3c | ||
|
|
5422b3d8bd | ||
|
|
0273390d79 | ||
|
|
8f7242b939 | ||
|
|
246dee63b4 | ||
|
|
697918a73a | ||
|
|
67e480220e | ||
|
|
5784eb008e | ||
|
|
82d66c4d57 |
No files matched your search
@@ -64,7 +64,7 @@ body:
|
||||
description: 您可以通过设置-高级-导出日志来获得日志文件 / You can get logs file in Settings - Advanced - Dump logcat
|
||||
placeholder: 06-15 17:44:53.704 23382 23382 E ...
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: more
|
||||
@@ -79,6 +79,15 @@ body:
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: site
|
||||
attributes:
|
||||
label: 浏览站点 / Browsing site
|
||||
description: E-Hentai / ExHentai
|
||||
placeholder: E-Hentai
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
|
||||
@@ -76,7 +76,7 @@ and [Dynamic Color](https://m3.material.io/styles/color/dynamic-color/overview)
|
||||
| Flavor | Feature |
|
||||
|-|-|
|
||||
| Default | Android 9+, full support |
|
||||
| Marshmallow | Android 6.0+, limited support* |
|
||||
| Marshmallow | Android 6.0-8.1, limited support* |
|
||||
| OSS | FOSS dependencies** |
|
||||
| GMS | Proprietary GMS component |
|
||||
|
||||
|
||||
@@ -64,8 +64,8 @@ android {
|
||||
applicationId = "moe.tarsin.ehviewer"
|
||||
minSdk = 28
|
||||
targetSdk = 34
|
||||
versionCode = 180044
|
||||
versionName = "1.8.10.2"
|
||||
versionCode = 180045
|
||||
versionName = "1.8.11.0"
|
||||
resourceConfigurations.addAll(
|
||||
listOf(
|
||||
"zh",
|
||||
@@ -231,7 +231,6 @@ dependencies {
|
||||
|
||||
implementation(libs.androidx.swiperefreshlayout)
|
||||
implementation(libs.androidx.work.runtime)
|
||||
implementation(libs.drawer)
|
||||
implementation(libs.photoview) // Dead Dependency
|
||||
implementation(libs.directionalviewpager) // Dead Dependency
|
||||
// https://github.com/google/accompanist/releases
|
||||
@@ -249,7 +248,6 @@ dependencies {
|
||||
implementation(libs.aboutlibraries.core)
|
||||
|
||||
implementation(libs.insetter) // Dead Dependency
|
||||
implementation(libs.bundles.rikkax)
|
||||
|
||||
implementation(platform(libs.arrow.stack))
|
||||
implementation(libs.arrow.fx.coroutines)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.hippo.ehviewer.cronet
|
||||
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT
|
||||
import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import java.nio.ByteBuffer
|
||||
import kotlin.contracts.InvocationKind
|
||||
@@ -26,7 +26,7 @@ val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
||||
val cache = (appCtx.cacheDir.toOkioPath() / "http_cache").toFile().apply { mkdirs() }
|
||||
setStoragePath(cache.absolutePath)
|
||||
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 100 * 1024)
|
||||
setUserAgent(CHROME_USER_AGENT)
|
||||
setUserAgent(Settings.userAgent)
|
||||
}.build()
|
||||
|
||||
class CronetRequest {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package androidx.navigation.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.MenuItem
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import androidx.navigation.NavController
|
||||
@@ -10,9 +9,10 @@ import androidx.navigation.NavDestination.Companion.hierarchy
|
||||
import androidx.navigation.NavOptions
|
||||
|
||||
@SuppressLint("PrivateResource")
|
||||
fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boolean {
|
||||
fun onNavDestinationSelected2(@IdRes id: Int, navController: NavController): Boolean {
|
||||
if (navController.currentDestination?.matchDestination(id) == true) return true
|
||||
val builder = NavOptions.Builder().setLaunchSingleTop(true)
|
||||
if (navController.currentDestination!!.parent!!.findNode(item.itemId) is ActivityNavigator.Destination) {
|
||||
if (navController.currentDestination!!.parent!!.findNode(id) is ActivityNavigator.Destination) {
|
||||
builder.setEnterAnim(R.anim.nav_default_enter_anim)
|
||||
.setExitAnim(R.anim.nav_default_exit_anim)
|
||||
.setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
|
||||
@@ -26,9 +26,9 @@ fun onNavDestinationSelected2(item: MenuItem, navController: NavController): Boo
|
||||
val options = builder.build()
|
||||
return try {
|
||||
// TODO provide proper API instead of using Exceptions as Control-Flow.
|
||||
navController.navigate(item.itemId, null, options)
|
||||
navController.navigate(id, null, options)
|
||||
// Return true only if the destination we've navigated to matches the MenuItem
|
||||
navController.currentDestination?.matchDestination(item.itemId) == true
|
||||
navController.currentDestination?.matchDestination(id) == true
|
||||
} catch (e: IllegalArgumentException) {
|
||||
false
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import com.hippo.ehviewer.util.isCronetSupported
|
||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||
import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.kt.unreachable
|
||||
@@ -91,7 +92,7 @@ class EhApplication : Application(), ImageLoaderFactory {
|
||||
migrateCookies()
|
||||
}
|
||||
launchIO {
|
||||
EhTagDatabase.update()
|
||||
EhTagDatabase
|
||||
}
|
||||
launchIO {
|
||||
EhDB
|
||||
@@ -170,6 +171,7 @@ class EhApplication : Application(), ImageLoaderFactory {
|
||||
dns(AsyncDns.toDns(AndroidAsyncDns.IPv4, AndroidAsyncDns.IPv6))
|
||||
}
|
||||
chunker { alwaysReadResponseBody(false) }
|
||||
addInterceptor(UncaughtExceptionInterceptor())
|
||||
addInterceptor(CloudflareInterceptor(appCtx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
package com.hippo.ehviewer
|
||||
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import java.util.Locale
|
||||
@@ -24,6 +25,7 @@ object Settings : DataStorePreferences(null) {
|
||||
var favCount by intArrayPref("fav_count", 10).emitTo(_favFlow)
|
||||
var favCloudCount by intPref("fav_cloud", 0).emitTo(_favFlow)
|
||||
|
||||
val needSignInFlow: Flow<Boolean>
|
||||
var downloadScheme by stringOrNullPref("image_scheme", null)
|
||||
var downloadAuthority by stringOrNullPref("image_authority", null)
|
||||
var downloadPath by stringOrNullPref("image_path", null)
|
||||
@@ -41,6 +43,7 @@ object Settings : DataStorePreferences(null) {
|
||||
var thumbResolution by intPref("thumb_resolution_2", 0)
|
||||
var readCacheSize by intPref("read_cache_size_2", 640)
|
||||
var launchPage by intPref("launch_page_2", 0)
|
||||
var commentThreshold by intPref("comment_threshold", -101)
|
||||
var forceEhThumb by boolPref("force_eh_thumb", false)
|
||||
var showComments by boolPref("show_gallery_comments", true)
|
||||
var requestNews by boolPref("request_news", false).observed { updateWhenRequestNewsChanges() }
|
||||
@@ -59,7 +62,7 @@ object Settings : DataStorePreferences(null) {
|
||||
var saveCrashLog by boolPref("save_crash_log", true)
|
||||
var security by boolPref("require_unlock", false)
|
||||
var removeImageFiles by boolPref("include_pic", true)
|
||||
var needSignIn by boolPref("need_sign_in", true)
|
||||
var needSignIn by boolPref("need_sign_in", true).also { needSignInFlow = it.valueFlow() }
|
||||
var blackDarkTheme by boolPref("black_dark_theme", false).observed { updateWhenAmoledModeChanges() }
|
||||
var harmonizeCategoryColor by boolPref("harmonize_category_color", true)
|
||||
var preloadThumbAggressively by boolPref("preload_thumb_aggressively", false)
|
||||
@@ -75,6 +78,8 @@ object Settings : DataStorePreferences(null) {
|
||||
var requestNewsTimerHour by intPref("request_news_timer_hour", -1)
|
||||
var requestNewsTimerMinute by intPref("request_news_timer_minute", -1)
|
||||
var updateIntervalDays by intPref("update_interval_days", 0)
|
||||
var recentToplist by stringPref("recent_toplist", "11")
|
||||
var userAgent by stringPref("user_agent", CHROME_USER_AGENT)
|
||||
var recentDownloadLabel by stringOrNullPref("recent_download_label", null)
|
||||
var defaultDownloadLabel by stringOrNullPref("default_download_label", null)
|
||||
var displayName by stringOrNullPref("display_name", null)
|
||||
|
||||
@@ -485,7 +485,7 @@ object EhEngine {
|
||||
var hasPages = false
|
||||
var hasRated = false
|
||||
for (gi in list) {
|
||||
if (gi.simpleTags != null) {
|
||||
if (!gi.simpleTags.isNullOrEmpty()) {
|
||||
hasTags = true
|
||||
}
|
||||
if (gi.pages != 0) {
|
||||
|
||||
@@ -17,17 +17,17 @@ object EhFilter : CoroutineScope {
|
||||
val filters = async { EhDB.getAllFilter() as MutableList }
|
||||
private suspend inline fun anyActive(mode: FilterMode, predicate: (Filter) -> Boolean) = filters.await().any { it.mode == mode && it.enable && predicate(it) }
|
||||
private fun <R> Filter.launchOps(
|
||||
callback: ((R) -> Unit)? = null,
|
||||
callback: (suspend (R) -> Unit)? = null,
|
||||
ops: suspend Filter.() -> R,
|
||||
) = launch { ops().let { callback?.invoke(it) } }
|
||||
fun Filter.remember(callback: ((Boolean) -> Unit)? = null) = launchOps(callback) {
|
||||
fun Filter.remember(callback: (suspend (Boolean) -> Unit)? = null) = launchOps(callback) {
|
||||
EhDB.addFilter(this).also { if (it) filters.await().add(this) }
|
||||
}
|
||||
fun Filter.trigger(callback: ((Unit) -> Unit)? = null) = launchOps(callback) {
|
||||
fun Filter.trigger(callback: (suspend (Unit) -> Unit)? = null) = launchOps(callback) {
|
||||
enable = !enable
|
||||
EhDB.updateFilter(this)
|
||||
}
|
||||
fun Filter.forget(callback: ((Unit) -> Unit)? = null) = launchOps(callback) {
|
||||
fun Filter.forget(callback: (suspend (Unit) -> Unit)? = null) = launchOps(callback) {
|
||||
EhDB.deleteFilter(this)
|
||||
filters.await().remove(this)
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.util.Log
|
||||
import com.hippo.ehviewer.EhApplication.Companion.baseOkHttpClient
|
||||
import com.hippo.ehviewer.EhApplication.Companion.noRedirectOkHttpClient
|
||||
import com.hippo.ehviewer.EhApplication.Companion.okHttpClient
|
||||
import com.hippo.ehviewer.Settings
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.resume
|
||||
@@ -44,7 +45,7 @@ import okhttp3.executeAsync
|
||||
import okio.BufferedSource
|
||||
|
||||
inline fun ehRequest(url: String, referer: String? = null, origin: String? = null, builder: Request.Builder.() -> Unit = {}) = Request.Builder().url(url).apply {
|
||||
addHeader("User-Agent", CHROME_USER_AGENT)
|
||||
addHeader("User-Agent", Settings.userAgent)
|
||||
addHeader("Accept", CHROME_ACCEPT)
|
||||
addHeader("Accept-Language", CHROME_ACCEPT_LANGUAGE)
|
||||
referer?.let { addHeader("Referer", it) }
|
||||
|
||||
@@ -55,7 +55,6 @@ object EhTagDatabase : CoroutineScope {
|
||||
)
|
||||
private lateinit var tagGroups: Map<String, Map<String, String>>
|
||||
private val updateLock = Mutex()
|
||||
private val dbLock = Mutex()
|
||||
override val coroutineContext = Dispatchers.IO + Job()
|
||||
|
||||
var initialized by mutableStateOf(false)
|
||||
@@ -202,42 +201,42 @@ object EhTagDatabase : CoroutineScope {
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun issueUpdateInMemoryData() {
|
||||
dbLock.withLock {
|
||||
getMetadata(appCtx)?.let { urls ->
|
||||
val dataName = urls[2]
|
||||
val dir = AppConfig.getFilesDir("tag-translations")
|
||||
val dataFile = File(dir, dataName).takeIf { it.exists() } ?: return
|
||||
runSuspendCatching {
|
||||
tagGroups = dataFile.source().buffer().use(BufferedSource::parseAs)
|
||||
initialized = true
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun issueUpdateInMemoryData(file: File? = null) {
|
||||
val dataFile = file ?: getMetadata(appCtx)?.let { metadata ->
|
||||
val dataName = metadata[2]
|
||||
val dir = AppConfig.getFilesDir("tag-translations")
|
||||
File(dir, dataName).takeIf { it.exists() }
|
||||
} ?: return
|
||||
tagGroups = dataFile.source().buffer().use(BufferedSource::parseAs)
|
||||
initialized = true
|
||||
}
|
||||
|
||||
init {
|
||||
launch {
|
||||
issueUpdateInMemoryData()
|
||||
updateLock.withLock {
|
||||
runSuspendCatching {
|
||||
issueUpdateInMemoryData()
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
updateInternal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateInternal() {
|
||||
val urls = getMetadata(appCtx)
|
||||
urls?.let {
|
||||
val sha1Name = urls[0]
|
||||
val sha1Url = urls[1]
|
||||
val dataName = urls[2]
|
||||
val dataUrl = urls[3]
|
||||
getMetadata(appCtx)?.let { metadata ->
|
||||
val sha1Name = metadata[0]
|
||||
val sha1Url = metadata[1]
|
||||
val dataName = metadata[2]
|
||||
val dataUrl = metadata[3]
|
||||
|
||||
val dir = AppConfig.getFilesDir("tag-translations")
|
||||
checkNotNull(dir)
|
||||
val sha1File = File(dir, sha1Name)
|
||||
val dataFile = File(dir, dataName)
|
||||
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
// Check current sha1 and current data
|
||||
val sha1 = getFileContent(sha1File)
|
||||
if (!checkData(sha1, dataFile)) {
|
||||
@@ -254,7 +253,7 @@ object EhTagDatabase : CoroutineScope {
|
||||
if (tempSha1 == sha1) {
|
||||
// The data is the same
|
||||
FileUtils.delete(tempSha1File)
|
||||
return@runCatching
|
||||
return
|
||||
}
|
||||
|
||||
// Save new data
|
||||
@@ -265,7 +264,7 @@ object EhTagDatabase : CoroutineScope {
|
||||
if (!checkData(tempSha1, tempDataFile)) {
|
||||
FileUtils.delete(tempSha1File)
|
||||
FileUtils.delete(tempDataFile)
|
||||
return@runCatching
|
||||
return
|
||||
}
|
||||
|
||||
// Replace current sha1 and current data with new sha1 and new data
|
||||
@@ -275,7 +274,7 @@ object EhTagDatabase : CoroutineScope {
|
||||
tempDataFile.renameTo(dataFile)
|
||||
|
||||
// Read new EhTagDatabase
|
||||
issueUpdateInMemoryData()
|
||||
issueUpdateInMemoryData(dataFile)
|
||||
}.onFailure {
|
||||
it.printStackTrace()
|
||||
}
|
||||
|
||||
@@ -146,10 +146,6 @@ object EhUtils {
|
||||
Settings.needSignIn = true
|
||||
}
|
||||
|
||||
fun needSignedIn(): Boolean {
|
||||
return Settings.needSignIn
|
||||
}
|
||||
|
||||
fun getSuitableTitle(gi: GalleryInfo): String {
|
||||
return if (Settings.showJpnTitle) {
|
||||
if (gi.titleJpn.isNullOrEmpty()) gi.title else gi.titleJpn
|
||||
|
||||
@@ -43,63 +43,24 @@ interface GalleryInfo {
|
||||
?: title?.let { generateSLangFromTitle(it) }
|
||||
}
|
||||
|
||||
private fun generateSLangFromTags(simpleTags: List<String>): String? {
|
||||
for (tag in simpleTags) {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_TAGS[i] == tag) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun generateSLangFromTitle(title: String): String? {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* ISO 639-1
|
||||
*/
|
||||
const val S_LANG_JA = "JA"
|
||||
const val S_LANG_EN = "EN"
|
||||
const val S_LANG_ZH = "ZH"
|
||||
const val S_LANG_NL = "NL"
|
||||
const val S_LANG_FR = "FR"
|
||||
const val S_LANG_DE = "DE"
|
||||
const val S_LANG_HU = "HU"
|
||||
const val S_LANG_IT = "IT"
|
||||
const val S_LANG_KO = "KO"
|
||||
const val S_LANG_PL = "PL"
|
||||
const val S_LANG_PT = "PT"
|
||||
const val S_LANG_RU = "RU"
|
||||
const val S_LANG_ES = "ES"
|
||||
const val S_LANG_TH = "TH"
|
||||
const val S_LANG_VI = "VI"
|
||||
|
||||
val S_LANGS = arrayOf(
|
||||
S_LANG_EN,
|
||||
S_LANG_ZH,
|
||||
S_LANG_ES,
|
||||
S_LANG_KO,
|
||||
S_LANG_RU,
|
||||
S_LANG_FR,
|
||||
S_LANG_PT,
|
||||
S_LANG_TH,
|
||||
S_LANG_DE,
|
||||
S_LANG_IT,
|
||||
S_LANG_VI,
|
||||
S_LANG_PL,
|
||||
S_LANG_HU,
|
||||
S_LANG_NL,
|
||||
"EN",
|
||||
"ZH",
|
||||
"ES",
|
||||
"KO",
|
||||
"RU",
|
||||
"FR",
|
||||
"PT",
|
||||
"TH",
|
||||
"DE",
|
||||
"IT",
|
||||
"VI",
|
||||
"PL",
|
||||
"HU",
|
||||
"NL",
|
||||
)
|
||||
val S_LANG_PATTERNS = arrayOf(
|
||||
private val S_LANG_PATTERNS = arrayOf(
|
||||
Pattern.compile(
|
||||
"[(\\[]eng(?:lish)?[)\\]]|英訳",
|
||||
Pattern.CASE_INSENSITIVE,
|
||||
@@ -131,7 +92,7 @@ interface GalleryInfo {
|
||||
Pattern.compile("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", Pattern.CASE_INSENSITIVE),
|
||||
Pattern.compile("[(\\[]dutch[)\\]]|オランダ翻訳", Pattern.CASE_INSENSITIVE),
|
||||
)
|
||||
val S_LANG_TAGS = arrayOf(
|
||||
private val S_LANG_TAGS = arrayOf(
|
||||
"language:english",
|
||||
"language:chinese",
|
||||
"language:spanish",
|
||||
@@ -148,6 +109,26 @@ interface GalleryInfo {
|
||||
"language:dutch",
|
||||
)
|
||||
|
||||
private fun generateSLangFromTags(simpleTags: List<String>): String? {
|
||||
for (tag in simpleTags) {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_TAGS[i] == tag) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun generateSLangFromTitle(title: String): String? {
|
||||
for (i in S_LANGS.indices) {
|
||||
if (S_LANG_PATTERNS[i].matcher(title).find()) {
|
||||
return S_LANGS[i]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
const val NOT_FAVORITED = -2
|
||||
const val LOCAL_FAVORITED = -1
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.hippo.ehviewer.client.parser
|
||||
|
||||
import arrow.core.unzip
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.EhFilter
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.EhUtils.getCategory
|
||||
@@ -394,7 +395,7 @@ object GalleryDetailParser {
|
||||
// filter comment
|
||||
if (!comment.uploader) {
|
||||
val sEhFilter = EhFilter
|
||||
if (sEhFilter.filterCommenter(comment.user!!) || sEhFilter.filterComment(comment.comment!!)) {
|
||||
if (comment.score <= Settings.commentThreshold || sEhFilter.filterCommenter(comment.user!!) || sEhFilter.filterComment(comment.comment!!)) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ import java.util.Locale
|
||||
class EhPageLoader(private val mGalleryInfo: GalleryInfo) : PageLoader2(), OnSpiderListener {
|
||||
private lateinit var mSpiderQueen: SpiderQueen
|
||||
override fun start() {
|
||||
mSpiderQueen = obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ)
|
||||
mSpiderQueen.addOnSpiderListener(this)
|
||||
if (!::mSpiderQueen.isInitialized) {
|
||||
mSpiderQueen = obtainSpiderQueen(mGalleryInfo, SpiderQueen.MODE_READ)
|
||||
mSpiderQueen.addOnSpiderListener(this)
|
||||
}
|
||||
}
|
||||
|
||||
override fun stop() {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
@file:Suppress("ktlint:standard:property-naming")
|
||||
|
||||
package com.hippo.ehviewer.icons
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
object EhIcons {
|
||||
object Filled
|
||||
|
||||
object Big {
|
||||
object Filled
|
||||
|
||||
val Default = Filled
|
||||
}
|
||||
|
||||
val Default = Filled
|
||||
}
|
||||
|
||||
inline fun bigIcon(
|
||||
name: String,
|
||||
block: ImageVector.Builder.() -> ImageVector.Builder,
|
||||
): ImageVector = ImageVector.Builder(
|
||||
name = name,
|
||||
defaultWidth = BigIconDimension.dp,
|
||||
defaultHeight = BigIconDimension.dp,
|
||||
viewportWidth = MaterialIconDimension,
|
||||
viewportHeight = MaterialIconDimension,
|
||||
).block().build()
|
||||
|
||||
const val BigIconDimension = 120f
|
||||
const val MaterialIconDimension = 24f
|
||||
@@ -0,0 +1,51 @@
|
||||
package com.hippo.ehviewer.icons.big
|
||||
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.bigIcon
|
||||
import com.hippo.ehviewer.util.unsafeLazy
|
||||
|
||||
val EhIcons.Big.Filled.History by unsafeLazy {
|
||||
bigIcon(name = "History") {
|
||||
materialPath {
|
||||
moveTo(5.5F, 2.0F)
|
||||
horizontalLineTo(22.0F)
|
||||
verticalLineTo(0.0F)
|
||||
horizontalLineTo(5.0F)
|
||||
curveTo(3.3F, 0.0F, 2.0F, 1.3F, 2.0F, 3.0F)
|
||||
verticalLineTo(21.0F)
|
||||
curveTo(2.0F, 22.7F, 3.3F, 24.0F, 5.0F, 24.0F)
|
||||
horizontalLineTo(22.0F)
|
||||
verticalLineTo(4.0F)
|
||||
horizontalLineTo(5.5F)
|
||||
curveTo(4.1F, 4.0F, 4.1F, 2.0F, 5.5F, 2.0F)
|
||||
close()
|
||||
moveTo(20.0F, 6.0F)
|
||||
verticalLineTo(22.0F)
|
||||
horizontalLineTo(6.0F)
|
||||
verticalLineTo(6.0F)
|
||||
horizontalLineTo(20.0F)
|
||||
moveTo(12.5F, 11.8F)
|
||||
verticalLineTo(14.4F)
|
||||
lineTo(14.9F, 15.8F)
|
||||
lineTo(15.3F, 15.2F)
|
||||
lineTo(13.3F, 14.0F)
|
||||
verticalLineTo(11.9F)
|
||||
moveTo(13.3F, 9.3F)
|
||||
curveTo(11.5F, 9.3F, 10.0F, 10.3F, 9.2F, 11.7F)
|
||||
lineTo(8.0F, 10.5F)
|
||||
verticalLineTo(13.8F)
|
||||
horizontalLineTo(11.3F)
|
||||
lineTo(9.9F, 12.4F)
|
||||
curveTo(10.5F, 11.1F, 11.8F, 10.3F, 13.3F, 10.3F)
|
||||
curveTo(15.4F, 10.3F, 17.1F, 12.0F, 17.1F, 14.1F)
|
||||
reflectiveCurveTo(15.4F, 17.9F, 13.3F, 17.9F)
|
||||
curveTo(11.7F, 17.9F, 10.3F, 16.9F, 9.8F, 15.4F)
|
||||
horizontalLineTo(8.7F)
|
||||
curveTo(9.3F, 17.4F, 11.1F, 18.9F, 13.3F, 18.9F)
|
||||
curveTo(15.9F, 18.9F, 18.1F, 16.8F, 18.1F, 14.1F)
|
||||
curveTo(18.0F, 11.4F, 15.9F, 9.3F, 13.3F, 9.3F)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.hippo.ehviewer.icons.big
|
||||
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.bigIcon
|
||||
import com.hippo.ehviewer.util.unsafeLazy
|
||||
|
||||
val EhIcons.Big.Filled.SadAndroid by unsafeLazy {
|
||||
bigIcon(name = "SadAndroid") {
|
||||
materialPath {
|
||||
moveTo(16.5F, 10.8F)
|
||||
curveToRelative(-0.61F, 0.0F, -1.1F, 0.49F, -1.1F, 1.1F)
|
||||
curveToRelative(0.0F, 0.61F, 0.49F, 1.1F, 1.1F, 1.1F)
|
||||
reflectiveCurveToRelative(1.1F, -0.49F, 1.1F, -1.1F)
|
||||
curveTo(17.6F, 11.29F, 17.11F, 10.8F, 16.5F, 10.8F)
|
||||
close()
|
||||
moveTo(8.2F, 10.1F)
|
||||
curveToRelative(-0.61F, 0.0F, -1.1F, 0.49F, -1.1F, 1.1F)
|
||||
reflectiveCurveToRelative(0.49F, 1.1F, 1.1F, 1.1F)
|
||||
reflectiveCurveToRelative(1.1F, -0.49F, 1.1F, -1.1F)
|
||||
reflectiveCurveTo(8.81F, 10.1F, 8.2F, 10.1F)
|
||||
close()
|
||||
moveTo(22.1F, 16.4F)
|
||||
curveToRelative(-0.46F, -3.33F, -2.24F, -5.93F, -4.81F, -7.45F)
|
||||
lineTo(19.5F, 6.3F)
|
||||
curveToRelative(0.1F, -0.1F, 0.1F, -0.3F, 0.0F, -0.4F)
|
||||
curveToRelative(-0.1F, -0.1F, -0.3F, -0.1F, -0.4F, 0.0F)
|
||||
lineToRelative(-2.27F, 2.79F)
|
||||
curveToRelative(-1.26F, -0.66F, -2.69F, -1.07F, -4.23F, -1.19F)
|
||||
curveToRelative(-1.4F, -0.15F, -2.77F, -0.01F, -4.05F, 0.37F)
|
||||
lineTo(6.7F, 4.7F)
|
||||
curveTo(6.6F, 4.6F, 6.4F, 4.5F, 6.3F, 4.6F)
|
||||
curveTo(6.2F, 4.7F, 6.1F, 4.9F, 6.2F, 5.0F)
|
||||
lineToRelative(1.79F, 3.05F)
|
||||
curveToRelative(-2.83F, 1.02F, -5.12F, 3.3F, -6.19F, 6.55F)
|
||||
lineToRelative(15.44F, 1.37F)
|
||||
curveToRelative(0.0F, 0.02F, -0.01F, 0.04F, -0.01F, 0.05F)
|
||||
curveToRelative(0.0F, 0.59F, 0.48F, 1.07F, 1.07F, 1.07F)
|
||||
curveToRelative(0.54F, 0.0F, 0.97F, -0.41F, 1.04F, -0.93F)
|
||||
lineTo(22.1F, 16.4F)
|
||||
close()
|
||||
moveTo(2.63F, 14.07F)
|
||||
curveToRelative(1.51F, -3.74F, 4.83F, -6.03F, 8.84F, -6.03F)
|
||||
curveToRelative(0.35F, 0.0F, 0.71F, 0.02F, 1.07F, 0.06F)
|
||||
curveToRelative(4.55F, 0.36F, 7.94F, 3.33F, 8.84F, 7.64F)
|
||||
lineToRelative(-2.12F, -0.19F)
|
||||
curveToRelative(-0.27F, -0.68F, -0.95F, -1.46F, -0.95F, -1.46F)
|
||||
reflectiveCurveToRelative(-0.58F, 0.66F, -0.89F, 1.29F)
|
||||
lineTo(2.63F, 14.07F)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.hippo.ehviewer.icons.filled
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.util.unsafeLazy
|
||||
|
||||
val EhIcons.Filled.SadPanda by unsafeLazy {
|
||||
materialIcon(name = "SadPanda") {
|
||||
materialPath {
|
||||
moveTo(21.1F, 7.8F)
|
||||
curveTo(22.2F, 7.4F, 23.0F, 6.3F, 23.0F, 5.0F)
|
||||
curveTo(23.0F, 3.3F, 21.7F, 2.0F, 20.0F, 2.0F)
|
||||
curveTo(18.9F, 2.0F, 17.9F, 2.6F, 17.4F, 3.6F)
|
||||
lineTo(21.1F, 7.8F)
|
||||
moveTo(6.6F, 3.6F)
|
||||
curveTo(6.1F, 2.6F, 5.1F, 2.0F, 4.0F, 2.0F)
|
||||
curveTo(2.3F, 2.0F, 1.0F, 3.3F, 1.0F, 5.0F)
|
||||
curveTo(1.0F, 6.3F, 1.8F, 7.4F, 2.9F, 7.8F)
|
||||
lineTo(6.6F, 3.6F)
|
||||
moveTo(10.1F, 13.0F)
|
||||
lineTo(8.0F, 15.1F)
|
||||
curveTo(7.4F, 15.7F, 6.5F, 15.7F, 5.9F, 15.1F)
|
||||
lineTo(5.9F, 15.1F)
|
||||
curveTo(5.3F, 14.5F, 5.3F, 13.6F, 5.9F, 13.0F)
|
||||
lineTo(8.0F, 10.9F)
|
||||
curveTo(8.6F, 10.3F, 9.5F, 10.3F, 10.1F, 10.9F)
|
||||
lineTo(10.1F, 10.9F)
|
||||
curveTo(10.7F, 11.5F, 10.7F, 12.4F, 10.1F, 13.0F)
|
||||
moveTo(16.0F, 15.1F)
|
||||
lineTo(13.9F, 13.0F)
|
||||
curveTo(13.3F, 12.4F, 13.3F, 11.5F, 13.9F, 10.9F)
|
||||
lineTo(13.9F, 10.9F)
|
||||
curveTo(14.5F, 10.3F, 15.4F, 10.3F, 16.0F, 10.9F)
|
||||
lineTo(18.1F, 13.0F)
|
||||
curveTo(18.7F, 13.6F, 18.7F, 14.5F, 18.1F, 15.1F)
|
||||
lineTo(18.1F, 15.1F)
|
||||
curveTo(17.5F, 15.7F, 16.6F, 15.7F, 16.0F, 15.1F)
|
||||
moveTo(12.0F, 17.0F)
|
||||
curveTo(12.8F, 17.0F, 13.5F, 17.4F, 13.5F, 18.0F)
|
||||
reflectiveCurveTo(12.8F, 19.0F, 12.0F, 19.0F)
|
||||
reflectiveCurveTo(10.5F, 18.6F, 10.5F, 18.0F)
|
||||
reflectiveCurveTo(11.2F, 17.0F, 12.0F, 17.0F)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.hippo.ehviewer.icons.filled
|
||||
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.util.unsafeLazy
|
||||
|
||||
val EhIcons.Filled.Subscriptions by unsafeLazy {
|
||||
materialIcon(name = "Subscriptions") {
|
||||
materialPath {
|
||||
moveTo(20.0F, 8.0F)
|
||||
horizontalLineTo(4.0F)
|
||||
verticalLineTo(6.0F)
|
||||
horizontalLineToRelative(16.0F)
|
||||
verticalLineTo(8.0F)
|
||||
moveTo(18.0F, 2.0F)
|
||||
horizontalLineTo(6.0F)
|
||||
verticalLineToRelative(2.0F)
|
||||
horizontalLineToRelative(12.0F)
|
||||
verticalLineTo(2.0F)
|
||||
moveTo(20.0F, 10.0F)
|
||||
horizontalLineTo(4.0F)
|
||||
curveToRelative(-1.1F, 0.0F, -2.0F, 0.9F, -2.0F, 2.0F)
|
||||
verticalLineToRelative(8.0F)
|
||||
curveToRelative(0.0F, 1.1F, 0.9F, 2.0F, 2.0F, 2.0F)
|
||||
horizontalLineToRelative(16.0F)
|
||||
curveToRelative(1.1F, 0.0F, 2.0F, -0.9F, 2.0F, -2.0F)
|
||||
verticalLineToRelative(-8.0F)
|
||||
curveTo(22.0F, 10.9F, 21.1F, 10.0F, 20.0F, 10.0F)
|
||||
close()
|
||||
moveTo(11.0F, 14.0F)
|
||||
horizontalLineTo(7.0F)
|
||||
verticalLineToRelative(1.0F)
|
||||
horizontalLineToRelative(4.0F)
|
||||
verticalLineToRelative(2.0F)
|
||||
horizontalLineTo(7.0F)
|
||||
verticalLineToRelative(1.0F)
|
||||
horizontalLineToRelative(4.0F)
|
||||
verticalLineToRelative(2.0F)
|
||||
horizontalLineTo(5.0F)
|
||||
verticalLineToRelative(-8.0F)
|
||||
horizontalLineToRelative(6.0F)
|
||||
verticalLineTo(14.0F)
|
||||
close()
|
||||
moveTo(19.0F, 20.0F)
|
||||
horizontalLineToRelative(-2.0F)
|
||||
verticalLineToRelative(-3.0F)
|
||||
horizontalLineToRelative(-2.0F)
|
||||
verticalLineToRelative(3.0F)
|
||||
horizontalLineToRelative(-2.0F)
|
||||
verticalLineToRelative(-8.0F)
|
||||
horizontalLineToRelative(2.0F)
|
||||
verticalLineToRelative(3.0F)
|
||||
horizontalLineToRelative(2.0F)
|
||||
verticalLineToRelative(-3.0F)
|
||||
horizontalLineToRelative(2.0F)
|
||||
verticalLineTo(20.0F)
|
||||
close()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,7 @@ class Image private constructor(drawable: Drawable, private val src: AutoCloseab
|
||||
var mObtainedDrawable: Drawable? = drawable
|
||||
private set
|
||||
|
||||
val size: Int
|
||||
get() = mObtainedDrawable!!.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 }
|
||||
val size = drawable.run { intrinsicHeight * intrinsicWidth * 4 * if (this is Animatable) 4 else 1 }
|
||||
|
||||
@Synchronized
|
||||
fun recycle() {
|
||||
|
||||
@@ -59,7 +59,7 @@ class SpiderDen(mGalleryInfo: GalleryInfo) {
|
||||
suspend fun setMode(value: Int) {
|
||||
mode = value
|
||||
if (mode == SpiderQueen.MODE_DOWNLOAD && downloadDir == null) {
|
||||
downloadDir = getGalleryDownloadDir(mGid)!!.apply { check(ensureDir()) { "Download directory $uri is not valid directory!" } }
|
||||
downloadDir = getGalleryDownloadDir(mGid)?.takeIf { it.ensureDir() }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,7 @@ import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.util.FavouriteStatusRouter
|
||||
import com.hippo.ehviewer.util.LongList
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.requestPermission
|
||||
import com.hippo.unifile.UniFile
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
@@ -68,7 +69,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
import rikka.core.util.ContextUtils.requireActivity
|
||||
import splitties.init.appCtx
|
||||
|
||||
object CommonOperations {
|
||||
@@ -314,7 +314,7 @@ suspend fun DialogState.doGalleryInfoAction(info: BaseGalleryInfo, context: Cont
|
||||
title = EhUtils.getSuitableTitle(info),
|
||||
)
|
||||
}
|
||||
with(requireActivity<MainActivity>(context)) {
|
||||
with(context.findActivity<MainActivity>()) {
|
||||
when (selected) {
|
||||
0 -> navToReader(info)
|
||||
1 -> withUIContext {
|
||||
|
||||
@@ -19,13 +19,12 @@ import android.content.res.Resources.Theme
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.StyleRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.navigation.ActivityNavigator
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import eu.kanade.tachiyomi.util.system.isNightMode
|
||||
import eu.kanade.tachiyomi.util.view.setSecureScreen
|
||||
import rikka.insets.WindowInsetsHelper
|
||||
import rikka.layoutinflater.view.LayoutInflaterFactory
|
||||
|
||||
abstract class EhActivity : AppCompatActivity() {
|
||||
@StyleRes
|
||||
@@ -43,8 +42,9 @@ abstract class EhActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
layoutInflater.factory2 = LayoutInflaterFactory(delegate).addOnViewCreatedListener(WindowInsetsHelper.LISTENER)
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightStatusBars = !isNightMode()
|
||||
WindowCompat.getInsetsController(window, window.decorView).isAppearanceLightNavigationBars = !isNightMode()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@@ -32,7 +32,7 @@ import com.hippo.ehviewer.client.data.GalleryInfo.Companion.LOCAL_FAVORITED
|
||||
import com.hippo.ehviewer.client.thumbUrl
|
||||
import com.hippo.ehviewer.databinding.InfoSheetBinding
|
||||
import com.hippo.ehviewer.ui.scene.navWithUrl
|
||||
import com.hippo.ehviewer.util.tellClipboardWithToast
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
|
||||
private const val INDEX_URL = 2
|
||||
private const val INDEX_PARENT = 9
|
||||
@@ -83,7 +83,7 @@ class GalleryInfoBottomSheet(private val detail: GalleryDetail) : BottomSheetDia
|
||||
navController.navWithUrl(content)
|
||||
}
|
||||
} else {
|
||||
context tellClipboardWithToast content
|
||||
context.addTextToClipboard(content, true)
|
||||
if (index == INDEX_URL) {
|
||||
// Save it to avoid detect the gallery
|
||||
Settings.clipboardTextHashCode = data[index].hashCode()
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.ui
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.assist.AssistContent
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
@@ -26,59 +25,111 @@ import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.material.icons.filled.FormatListNumbered
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.Whatshot
|
||||
import androidx.compose.material3.DrawerValue
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ModalDrawerSheet
|
||||
import androidx.compose.material3.ModalNavigationDrawer
|
||||
import androidx.compose.material3.NavigationDrawerItem
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.rememberDrawerState
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.currentRecomposeScope
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidViewBinding
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.customview.widget.Openable
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.NavigationUI
|
||||
import androidx.navigation.ui.onNavDestinationSelected2
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.Settings.launchPage
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder
|
||||
import com.hippo.ehviewer.client.parser.GalleryDetailUrlParser
|
||||
import com.hippo.ehviewer.client.parser.GalleryPageUrlParser
|
||||
import com.hippo.ehviewer.databinding.ActivityMainBinding
|
||||
import com.hippo.ehviewer.download.DownloadService
|
||||
import com.hippo.ehviewer.download.downloadLocation
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.filled.Subscriptions
|
||||
import com.hippo.ehviewer.image.Image.Companion.decodeBitmap
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import com.hippo.ehviewer.ui.scene.GalleryDetailScene
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene
|
||||
import com.hippo.ehviewer.ui.scene.GalleryListScene.Companion.toStartArgs
|
||||
import com.hippo.ehviewer.ui.scene.ProgressScene
|
||||
import com.hippo.ehviewer.ui.scene.navAnimated
|
||||
import com.hippo.ehviewer.ui.scene.navWithUrl
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
import com.hippo.ehviewer.util.buildWindowInsets
|
||||
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
||||
import com.hippo.ehviewer.util.getUrlFromClipboard
|
||||
import com.hippo.ehviewer.util.set
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.systemservices.clipboardManager
|
||||
import splitties.systemservices.connectivityManager
|
||||
|
||||
private val navItems = arrayOf(
|
||||
Triple(R.id.nav_homepage, R.string.homepage, Icons.Default.Home),
|
||||
Triple(R.id.nav_subscription, R.string.subscription, EhIcons.Default.Subscriptions),
|
||||
Triple(R.id.nav_whats_hot, R.string.whats_hot, Icons.Default.Whatshot),
|
||||
Triple(R.id.nav_toplist, R.string.toplist, Icons.Default.FormatListNumbered),
|
||||
Triple(R.id.nav_favourite, R.string.favourite, Icons.Default.Favorite),
|
||||
Triple(R.id.nav_history, R.string.history, Icons.Default.History),
|
||||
Triple(R.id.nav_downloads, R.string.downloads, Icons.Default.Download),
|
||||
Triple(R.id.nav_settings, R.string.settings, Icons.Default.Settings),
|
||||
)
|
||||
|
||||
class MainActivity : EhActivity() {
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var navController: NavController
|
||||
private val isInitializedFlow = MutableStateFlow(false)
|
||||
|
||||
private fun saveImageToTempFile(uri: Uri): File? {
|
||||
val bitmap = runCatching {
|
||||
@@ -97,17 +148,15 @@ class MainActivity : EhActivity() {
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
lifecycleScope.launchUI {
|
||||
isInitializedFlow.first { it }
|
||||
if (!handleIntent(intent)) {
|
||||
if (intent != null && Intent.ACTION_VIEW == intent.action) {
|
||||
if (intent.data != null) {
|
||||
val url = intent.data.toString()
|
||||
EditTextDialogBuilder(this@MainActivity, url, "")
|
||||
.setTitle(R.string.error_cannot_parse_the_url)
|
||||
.setPositiveButton(android.R.string.copy) { _: DialogInterface?, _: Int ->
|
||||
this@MainActivity.addTextToClipboard(
|
||||
url,
|
||||
false,
|
||||
)
|
||||
.setPositiveButton(android.R.string.copy) { _, _ ->
|
||||
this@MainActivity.addTextToClipboard(url)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
@@ -161,58 +210,106 @@ class MainActivity : EhActivity() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setNavGraph() {
|
||||
navController.apply {
|
||||
graph = navInflater.inflate(R.navigation.nav_graph).apply {
|
||||
when (
|
||||
when (val value = launchPage) {
|
||||
0 -> GalleryListScene.ACTION_HOMEPAGE
|
||||
1 -> GalleryListScene.ACTION_SUBSCRIPTION
|
||||
2 -> GalleryListScene.ACTION_WHATS_HOT
|
||||
3 -> GalleryListScene.ACTION_TOP_LIST
|
||||
else -> throw IllegalStateException("Unexpected value: $value")
|
||||
}
|
||||
) {
|
||||
GalleryListScene.ACTION_HOMEPAGE -> setStartDestination(R.id.nav_homepage)
|
||||
GalleryListScene.ACTION_SUBSCRIPTION -> setStartDestination(R.id.nav_subscription)
|
||||
GalleryListScene.ACTION_WHATS_HOT -> setStartDestination(R.id.nav_whats_hot)
|
||||
GalleryListScene.ACTION_TOP_LIST -> setStartDestination(R.id.nav_toplist)
|
||||
}
|
||||
}
|
||||
}
|
||||
var drawerLocked by mutableStateOf(false)
|
||||
private var openDrawerFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
private var recomposeFlow = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
|
||||
|
||||
fun recompose() {
|
||||
recomposeFlow.tryEmit(Unit)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
val navHostFragment =
|
||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||
navController = navHostFragment.navController
|
||||
if (!EhUtils.needSignedIn()) setNavGraph()
|
||||
binding.drawView.addDrawerListener(mDrawerOnBackPressedCallback)
|
||||
binding.navView.setupWithNavController(navController)
|
||||
|
||||
// Trick: Tweak NavigationUI to disable multiple backstack
|
||||
binding.navView.setNavigationItemSelectedListener {
|
||||
val navigationView = binding.navView
|
||||
val handled = navigationView.checkedItem?.itemId == it.itemId || onNavDestinationSelected2(it, navController)
|
||||
if (handled) {
|
||||
val parent = navigationView.parent
|
||||
if (parent is Openable) {
|
||||
parent.close()
|
||||
} else {
|
||||
@SuppressLint("RestrictedApi")
|
||||
val bottomSheetBehavior = NavigationUI.findBottomSheetBehavior(navigationView)
|
||||
if (bottomSheetBehavior != null) {
|
||||
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
setMD3Content {
|
||||
val configuration = LocalConfiguration.current
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val scope = rememberCoroutineScope()
|
||||
val recomposeScope = currentRecomposeScope
|
||||
fun isSelected(id: Int) = ::navController.isInitialized && id == navController.currentDestination?.id
|
||||
fun closeDrawer() = scope.launch { drawerState.close() }
|
||||
LaunchedEffect(Unit) {
|
||||
openDrawerFlow.collect {
|
||||
drawerState.open()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
recomposeFlow.collect {
|
||||
recomposeScope.invalidate()
|
||||
}
|
||||
}
|
||||
BackHandler(drawerState.isOpen) {
|
||||
closeDrawer()
|
||||
}
|
||||
ModalNavigationDrawer(
|
||||
drawerContent = {
|
||||
ModalDrawerSheet(
|
||||
modifier = Modifier.widthIn(max = (configuration.screenWidthDp - 56).dp),
|
||||
windowInsets = WindowInsets(0, 0, 0, 0),
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier = Modifier.verticalScroll(scrollState).navigationBarsPadding(),
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.sadpanda_low_poly),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
contentScale = ContentScale.FillWidth,
|
||||
)
|
||||
navItems.forEach { (id, stringId, icon) ->
|
||||
NavigationDrawerItem(
|
||||
label = {
|
||||
Text(text = stringResource(id = stringId))
|
||||
},
|
||||
selected = isSelected(id),
|
||||
onClick = {
|
||||
closeDrawer()
|
||||
onNavDestinationSelected2(id, navController)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 12.dp),
|
||||
icon = {
|
||||
Icon(imageVector = icon, contentDescription = null)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
drawerState = drawerState,
|
||||
gesturesEnabled = !drawerLocked || drawerState.isOpen,
|
||||
) {
|
||||
val insets = buildWindowInsets {
|
||||
set(
|
||||
WindowInsetsCompat.Type.statusBars(),
|
||||
WindowInsets.statusBars,
|
||||
)
|
||||
set(
|
||||
WindowInsetsCompat.Type.navigationBars(),
|
||||
WindowInsets.navigationBars,
|
||||
)
|
||||
set(
|
||||
WindowInsetsCompat.Type.ime(),
|
||||
WindowInsets.ime,
|
||||
)
|
||||
}
|
||||
AndroidViewBinding(factory = { inflater, parent, attachToParent ->
|
||||
ActivityMainBinding.inflate(inflater, parent, attachToParent).apply {
|
||||
val navHostFragment = fragmentContainer.getFragment<NavHostFragment>()
|
||||
navController = navHostFragment.navController.apply {
|
||||
graph = navInflater.inflate(R.navigation.nav_graph).apply {
|
||||
check(launchPage in 0..3)
|
||||
setStartDestination(navItems[launchPage].first)
|
||||
}
|
||||
}
|
||||
isInitializedFlow.value = true
|
||||
}
|
||||
}) {
|
||||
ViewCompat.dispatchApplyWindowInsets(root, insets)
|
||||
}
|
||||
}
|
||||
handled
|
||||
}
|
||||
// Trick End
|
||||
|
||||
if (savedInstanceState == null) {
|
||||
if (intent.action != Intent.ACTION_MAIN) {
|
||||
@@ -220,7 +317,10 @@ class MainActivity : EhActivity() {
|
||||
}
|
||||
checkDownloadLocation()
|
||||
if (Settings.meteredNetworkWarning) {
|
||||
checkMeteredNetwork()
|
||||
lifecycleScope.launchUI {
|
||||
isInitializedFlow.first { it }
|
||||
checkMeteredNetwork()
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if (!Settings.appLinkVerifyTip) {
|
||||
@@ -282,7 +382,7 @@ class MainActivity : EhActivity() {
|
||||
if (connectivityManager.isActiveNetworkMetered) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
Snackbar.make(
|
||||
binding.drawView,
|
||||
findViewById(R.id.fragment_container),
|
||||
R.string.metered_network_warning,
|
||||
Snackbar.LENGTH_LONG,
|
||||
)
|
||||
@@ -298,25 +398,19 @@ class MainActivity : EhActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private val loginLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
setNavGraph()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
if (EhUtils.needSignedIn()) {
|
||||
loginLauncher.launch(Intent(this, ConfigureActivity::class.java))
|
||||
if (Settings.needSignIn) {
|
||||
startActivity(Intent(this, ConfigureActivity::class.java))
|
||||
}
|
||||
super.onResume()
|
||||
lifecycleScope.launch {
|
||||
delay(300)
|
||||
mDrawerOnBackPressedCallback.remove()
|
||||
onBackPressedDispatcher.addCallback(mDrawerOnBackPressedCallback)
|
||||
checkClipboardUrl()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun checkClipboardUrl() {
|
||||
isInitializedFlow.first { it }
|
||||
val text = clipboardManager.getUrlFromClipboard(this)
|
||||
val hashCode = text?.hashCode() ?: 0
|
||||
if (text != null && hashCode != 0 && Settings.clipboardTextHashCode != hashCode) {
|
||||
@@ -341,7 +435,7 @@ class MainActivity : EhActivity() {
|
||||
launch?.let {
|
||||
withUIContext {
|
||||
val snackbar = Snackbar.make(
|
||||
binding.drawView,
|
||||
findViewById(R.id.fragment_container),
|
||||
R.string.clipboard_gallery_url_snack_message,
|
||||
Snackbar.LENGTH_SHORT,
|
||||
)
|
||||
@@ -355,42 +449,8 @@ class MainActivity : EhActivity() {
|
||||
Settings.clipboardTextHashCode = hashCode
|
||||
}
|
||||
|
||||
fun addAboveSnackView(view: View) {
|
||||
binding.absnacker.addAboveSnackView(view)
|
||||
}
|
||||
|
||||
fun removeAboveSnackView(view: View) {
|
||||
binding.absnacker.removeAboveSnackView(view)
|
||||
}
|
||||
|
||||
fun setDrawerLockMode(lockMode: Int, edgeGravity: Int) {
|
||||
binding.drawView.setDrawerLockMode(lockMode, edgeGravity)
|
||||
}
|
||||
|
||||
fun getDrawerLockMode(edgeGravity: Int): Int {
|
||||
return binding.drawView.getDrawerLockMode(edgeGravity)
|
||||
}
|
||||
|
||||
fun isDrawerOpen(drawerGravity: Int) = binding.drawView.isDrawerOpen(drawerGravity)
|
||||
|
||||
fun openDrawer(drawerGravity: Int) {
|
||||
binding.drawView.openDrawer(drawerGravity)
|
||||
}
|
||||
|
||||
fun closeDrawer(drawerGravity: Int) {
|
||||
binding.drawView.closeDrawer(drawerGravity)
|
||||
}
|
||||
|
||||
fun toggleDrawer(drawerGravity: Int) {
|
||||
if (isDrawerOpen(drawerGravity)) {
|
||||
closeDrawer(drawerGravity)
|
||||
} else {
|
||||
openDrawer(drawerGravity)
|
||||
}
|
||||
}
|
||||
|
||||
fun clearNavCheckedItem() {
|
||||
binding.navView.setCheckedItem(R.id.nav_stub)
|
||||
fun openDrawer() {
|
||||
openDrawerFlow.tryEmit(Unit)
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int, useToast: Boolean = false) {
|
||||
@@ -401,17 +461,19 @@ class MainActivity : EhActivity() {
|
||||
* If activity is running, show snack bar, otherwise show toast
|
||||
*/
|
||||
fun showTip(message: CharSequence, length: Int, useToast: Boolean = false) {
|
||||
findViewById<View>(R.id.snackbar)?.takeUnless { useToast }?.apply {
|
||||
if (!useToast) {
|
||||
Snackbar.make(
|
||||
this,
|
||||
findViewById(R.id.fragment_container),
|
||||
message,
|
||||
if (length == BaseScene.LENGTH_LONG) Snackbar.LENGTH_LONG else Snackbar.LENGTH_SHORT,
|
||||
).show()
|
||||
} ?: Toast.makeText(
|
||||
this,
|
||||
message,
|
||||
if (length == BaseScene.LENGTH_LONG) Toast.LENGTH_LONG else Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
} else {
|
||||
Toast.makeText(
|
||||
this,
|
||||
message,
|
||||
if (length == BaseScene.LENGTH_LONG) Toast.LENGTH_LONG else Toast.LENGTH_SHORT,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
var mShareUrl: String? = null
|
||||
@@ -419,20 +481,4 @@ class MainActivity : EhActivity() {
|
||||
super.onProvideAssistContent(outContent)
|
||||
mShareUrl?.let { outContent?.webUri = Uri.parse(mShareUrl) }
|
||||
}
|
||||
|
||||
private val mDrawerOnBackPressedCallback =
|
||||
object : OnBackPressedCallback(false), DrawerLayout.DrawerListener {
|
||||
val slideThreshold = 0.05
|
||||
override fun handleOnBackPressed() {
|
||||
binding.drawView.closeDrawers()
|
||||
}
|
||||
|
||||
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
|
||||
isEnabled = slideOffset > slideThreshold
|
||||
}
|
||||
|
||||
override fun onDrawerOpened(drawerView: View) {}
|
||||
override fun onDrawerClosed(drawerView: View) {}
|
||||
override fun onDrawerStateChanged(newState: Int) {}
|
||||
}
|
||||
}
|
||||
@@ -1,217 +0,0 @@
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior
|
||||
import androidx.core.util.ObjectsCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.drawerlayout.widget.DrawerLayout.DrawerListener
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
|
||||
import com.drakeet.drawer.FullDraggableHelper
|
||||
import com.google.android.material.snackbar.Snackbar.SnackbarLayout
|
||||
import com.hippo.ehviewer.util.dp2px
|
||||
|
||||
class AboveSnackerLayout : FrameLayout, AttachedBehavior, FullDraggableHelper.Callback {
|
||||
private val helper: FullDraggableHelper
|
||||
private var drawerLayout: DrawerLayout? = null
|
||||
private var mAboveSnackViewList: MutableList<View>? = null
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) {
|
||||
helper = FullDraggableHelper(context, this)
|
||||
}
|
||||
|
||||
constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) :
|
||||
super(context!!, attrs, defStyle) {
|
||||
helper = FullDraggableHelper(context, this)
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ensureDrawerLayout()
|
||||
}
|
||||
|
||||
private fun ensureDrawerLayout() {
|
||||
val parent = parent.parent
|
||||
check(parent is DrawerLayout) { "This $this must be added to a DrawerLayout" }
|
||||
drawerLayout = parent
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
|
||||
return helper.onInterceptTouchEvent(event)
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
return helper.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun getDrawerMainContainer(): View {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun isDrawerOpen(gravity: Int): Boolean {
|
||||
return drawerLayout!!.isDrawerOpen(gravity)
|
||||
}
|
||||
|
||||
override fun hasEnabledDrawer(gravity: Int): Boolean {
|
||||
return drawerLayout!!.getDrawerLockMode(gravity) == DrawerLayout.LOCK_MODE_UNLOCKED &&
|
||||
findDrawerWithGravity(gravity) != null
|
||||
}
|
||||
|
||||
override fun offsetDrawer(gravity: Int, offset: Float) {
|
||||
setDrawerToOffset(gravity, offset)
|
||||
drawerLayout!!.invalidate()
|
||||
}
|
||||
|
||||
override fun smoothOpenDrawer(gravity: Int) {
|
||||
drawerLayout!!.openDrawer(gravity, true)
|
||||
}
|
||||
|
||||
override fun smoothCloseDrawer(gravity: Int) {
|
||||
drawerLayout!!.closeDrawer(gravity, true)
|
||||
}
|
||||
|
||||
override fun onDrawerDragging() {
|
||||
val drawerListeners = drawerListeners
|
||||
if (drawerListeners != null) {
|
||||
val listenerCount = drawerListeners.size
|
||||
for (i in listenerCount - 1 downTo 0) {
|
||||
drawerListeners[i].onDrawerStateChanged(DrawerLayout.STATE_DRAGGING)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val drawerListeners: List<DrawerListener>?
|
||||
get() = try {
|
||||
val listeners = DrawerLayout::class.java.getDeclaredField("mListeners")
|
||||
listeners.isAccessible = true
|
||||
listeners[drawerLayout] as List<DrawerListener>
|
||||
} catch (e: Exception) {
|
||||
// throw to let developer know the api is changed
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
||||
private fun setDrawerToOffset(gravity: Int, offset: Float) {
|
||||
val drawerView = findDrawerWithGravity(gravity)
|
||||
val slideOffsetPercent =
|
||||
(offset / ObjectsCompat.requireNonNull(drawerView).width).coerceIn(0f, 1f)
|
||||
try {
|
||||
val method = DrawerLayout::class.java.getDeclaredMethod(
|
||||
"moveDrawerToOffset",
|
||||
View::class.java,
|
||||
Float::class.javaPrimitiveType,
|
||||
)
|
||||
method.isAccessible = true
|
||||
method.invoke(drawerLayout, drawerView, slideOffsetPercent)
|
||||
drawerView!!.visibility = VISIBLE
|
||||
} catch (e: Exception) {
|
||||
// throw to let developer know the api is changed
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from DrawerLayout
|
||||
private fun findDrawerWithGravity(gravity: Int): View? {
|
||||
val absHorizontalGravity = GravityCompat.getAbsoluteGravity(
|
||||
gravity,
|
||||
ViewCompat.getLayoutDirection(drawerLayout!!),
|
||||
) and Gravity.HORIZONTAL_GRAVITY_MASK
|
||||
val childCount = drawerLayout!!.childCount
|
||||
for (i in 0 until childCount) {
|
||||
val child = drawerLayout!!.getChildAt(i)
|
||||
val childAbsGravity = getDrawerViewAbsoluteGravity(child)
|
||||
if (childAbsGravity and Gravity.HORIZONTAL_GRAVITY_MASK == absHorizontalGravity) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Copied from DrawerLayout
|
||||
private fun getDrawerViewAbsoluteGravity(drawerView: View): Int {
|
||||
val gravity = (drawerView.layoutParams as DrawerLayout.LayoutParams).gravity
|
||||
return GravityCompat.getAbsoluteGravity(
|
||||
gravity,
|
||||
ViewCompat.getLayoutDirection(drawerLayout!!),
|
||||
)
|
||||
}
|
||||
|
||||
fun addAboveSnackView(view: View) {
|
||||
if (null == mAboveSnackViewList) {
|
||||
mAboveSnackViewList = ArrayList()
|
||||
}
|
||||
mAboveSnackViewList!!.add(view)
|
||||
}
|
||||
|
||||
fun removeAboveSnackView(view: View) {
|
||||
if (null == mAboveSnackViewList) {
|
||||
return
|
||||
}
|
||||
mAboveSnackViewList!!.remove(view)
|
||||
}
|
||||
|
||||
val aboveSnackViewCount: Int
|
||||
get() = if (null == mAboveSnackViewList) 0 else mAboveSnackViewList!!.size
|
||||
|
||||
fun getAboveSnackViewAt(index: Int): View? {
|
||||
return if (null == mAboveSnackViewList || index < 0 || index >= mAboveSnackViewList!!.size) {
|
||||
null
|
||||
} else {
|
||||
mAboveSnackViewList!![index]
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBehavior(): Behavior {
|
||||
return Behavior()
|
||||
}
|
||||
|
||||
class Behavior : CoordinatorLayout.Behavior<AboveSnackerLayout>() {
|
||||
override fun layoutDependsOn(
|
||||
parent: CoordinatorLayout,
|
||||
child: AboveSnackerLayout,
|
||||
dependency: View,
|
||||
): Boolean {
|
||||
return dependency is SnackbarLayout
|
||||
}
|
||||
|
||||
override fun onDependentViewChanged(
|
||||
parent: CoordinatorLayout,
|
||||
child: AboveSnackerLayout,
|
||||
dependency: View,
|
||||
): Boolean {
|
||||
for (i in 0 until child.aboveSnackViewCount) {
|
||||
val view = child.getAboveSnackViewAt(i)
|
||||
if (view != null) {
|
||||
val translationY =
|
||||
(dependency.translationY - dependency.height - dp2px(view.context, 8f))
|
||||
.coerceAtMost(0f)
|
||||
ViewCompat.animate(view).setInterpolator(FastOutSlowInInterpolator())
|
||||
.translationY(translationY).setDuration(150).start()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onDependentViewRemoved(
|
||||
parent: CoordinatorLayout,
|
||||
child: AboveSnackerLayout,
|
||||
dependency: View,
|
||||
) {
|
||||
for (i in 0 until child.aboveSnackViewCount) {
|
||||
val view = child.getAboveSnackViewAt(i)
|
||||
if (view != null) {
|
||||
ViewCompat.animate(view).setInterpolator(FastOutSlowInInterpolator())
|
||||
.translationY(0f).setDuration(75).start()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,11 @@ import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.ElevatedCard
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.SecondaryTabRow
|
||||
import androidx.compose.material3.Tab
|
||||
import androidx.compose.material3.TabRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
|
||||
import androidx.compose.material3.windowsizeclass.calculateWindowSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
@@ -45,6 +47,7 @@ import com.hippo.ehviewer.ui.main.ImageSearch
|
||||
import com.hippo.ehviewer.ui.main.NormalSearch
|
||||
import com.hippo.ehviewer.ui.main.SearchAdvanced
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.pickVisualMedia
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@@ -71,6 +74,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val windowSizeClass = calculateWindowSizeClass(activity = context.findActivity())
|
||||
fun selectImage() = coroutineScope.launch {
|
||||
context.pickVisualMedia(ActivityResultContracts.PickVisualMedia.ImageOnly)?.let {
|
||||
vm.path = it.toString()
|
||||
@@ -93,6 +97,11 @@ class SearchLayout @JvmOverloads constructor(
|
||||
isAdvanced = vm.isAdvancedMode,
|
||||
onAdvancedChanged = { vm.isAdvancedMode = it },
|
||||
showInfo = { BaseDialogBuilder(context).setMessage(R.string.search_tip).show() },
|
||||
maxItemsInEachRow = when (windowSizeClass.widthSizeClass) {
|
||||
WindowWidthSizeClass.Compact -> 2
|
||||
WindowWidthSizeClass.Medium -> 3
|
||||
else -> 5
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -123,7 +132,7 @@ class SearchLayout @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
SecondaryTabRow(
|
||||
TabRow(
|
||||
selectedTabIndex = if (vm.isNormalMode) 0 else 1,
|
||||
divider = {},
|
||||
) {
|
||||
|
||||
@@ -26,7 +26,6 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
@@ -35,6 +34,8 @@ import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhUtils
|
||||
import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryInfo
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.big.SadAndroid
|
||||
|
||||
@Composable
|
||||
fun GalleryDetailHeaderInfoCard(
|
||||
@@ -160,7 +161,7 @@ fun GalleryDetailErrorTip(error: String, onClick: () -> Unit) = Column(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.big_sad_pandroid),
|
||||
imageVector = EhIcons.Big.Default.SadAndroid,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable(onClick = onClick),
|
||||
)
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.hippo.ehviewer.ui.main
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Help
|
||||
@@ -55,16 +57,24 @@ fun NormalSearch(
|
||||
isAdvanced: Boolean,
|
||||
onAdvancedChanged: (Boolean) -> Unit,
|
||||
showInfo: () -> Unit,
|
||||
maxItemsInEachRow: Int,
|
||||
) {
|
||||
FlowRow(modifier = Modifier.wrapContentHeight(align = Alignment.Top)) {
|
||||
FlowRow(
|
||||
modifier = Modifier.wrapContentHeight(align = Alignment.Top),
|
||||
maxItemsInEachRow = maxItemsInEachRow,
|
||||
) {
|
||||
categoryTable.forEach {
|
||||
val selected = category and it.first != 0
|
||||
FilterChip(
|
||||
selected = selected,
|
||||
onClick = { onCategoryChanged(if (selected) category xor it.first else category or it.first) },
|
||||
label = { Text(text = stringResource(id = it.second)) },
|
||||
leadingIcon = { if (selected) Icon(imageVector = Icons.Default.Check, contentDescription = null) },
|
||||
modifier = Modifier.padding(horizontal = 4.dp).align(alignment = Alignment.CenterVertically),
|
||||
leadingIcon = {
|
||||
AnimatedVisibility(selected) {
|
||||
Icon(imageVector = Icons.Default.Check, contentDescription = null)
|
||||
}
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 4.dp).align(alignment = Alignment.CenterVertically).widthIn(min = 145.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ import android.util.SparseArray
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.SoftwareKeyboardControllerCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelStoreOwner
|
||||
import androidx.lifecycle.setViewTreeLifecycleOwner
|
||||
@@ -41,60 +39,20 @@ abstract class BaseScene : Fragment() {
|
||||
private var drawerView: View? = null
|
||||
private var drawerViewState: SparseArray<Parcelable>? = null
|
||||
private var sideSheetDialog: SideSheetDialog? = null
|
||||
fun addAboveSnackView(view: View) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.addAboveSnackView(view)
|
||||
}
|
||||
|
||||
val isDrawerLocked
|
||||
get() = mainActivity?.drawerLocked == true
|
||||
|
||||
fun lockDrawer() {
|
||||
mainActivity?.drawerLocked = true
|
||||
}
|
||||
|
||||
fun removeAboveSnackView(view: View) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.removeAboveSnackView(view)
|
||||
}
|
||||
fun unlockDrawer() {
|
||||
mainActivity?.drawerLocked = false
|
||||
}
|
||||
|
||||
fun setDrawerLockMode(lockMode: Int, edgeGravity: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.setDrawerLockMode(lockMode, edgeGravity)
|
||||
}
|
||||
}
|
||||
|
||||
fun getDrawerLockMode(edgeGravity: Int): Int? {
|
||||
val activity = activity
|
||||
return if (activity is MainActivity) {
|
||||
activity.getDrawerLockMode(edgeGravity)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun isDrawerOpen(drawerGravity: Int): Boolean {
|
||||
val activity = activity
|
||||
return activity is MainActivity && activity.isDrawerOpen(drawerGravity)
|
||||
}
|
||||
|
||||
fun openDrawer(drawerGravity: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.openDrawer(drawerGravity)
|
||||
}
|
||||
}
|
||||
|
||||
fun closeDrawer(drawerGravity: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.closeDrawer(drawerGravity)
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleDrawer(drawerGravity: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.toggleDrawer(drawerGravity)
|
||||
}
|
||||
fun openDrawer() {
|
||||
mainActivity?.openDrawer()
|
||||
}
|
||||
|
||||
fun openSideSheet() {
|
||||
@@ -107,20 +65,14 @@ abstract class BaseScene : Fragment() {
|
||||
fun closeSideSheet() = sideSheetDialog!!.hide()
|
||||
|
||||
fun showTip(message: CharSequence?, length: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.showTip(message!!, length)
|
||||
}
|
||||
mainActivity?.showTip(message!!, length)
|
||||
}
|
||||
|
||||
fun showTip(@StringRes id: Int, length: Int) {
|
||||
val activity = activity
|
||||
if (activity is MainActivity) {
|
||||
activity.showTip(id, length)
|
||||
}
|
||||
mainActivity?.showTip(id, length)
|
||||
}
|
||||
|
||||
open val showLeftDrawer = false
|
||||
open val enableDrawerGestures = false
|
||||
|
||||
private fun createDrawerView(savedInstanceState: Bundle?): View? {
|
||||
drawerView = onCreateDrawerView(layoutInflater)
|
||||
@@ -156,10 +108,10 @@ abstract class BaseScene : Fragment() {
|
||||
view.background = requireActivity().theme.resolveDrawable(android.R.attr.windowBackground)
|
||||
|
||||
// Update left drawer locked state
|
||||
if (showLeftDrawer) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
if (enableDrawerGestures) {
|
||||
unlockDrawer()
|
||||
} else {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
lockDrawer()
|
||||
}
|
||||
hideSoftInput()
|
||||
createDrawerView(savedInstanceState)?.let {
|
||||
@@ -176,6 +128,7 @@ abstract class BaseScene : Fragment() {
|
||||
setContentView(it)
|
||||
}
|
||||
}
|
||||
mainActivity?.recompose()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -184,14 +137,7 @@ abstract class BaseScene : Fragment() {
|
||||
}
|
||||
|
||||
val mainActivity: MainActivity?
|
||||
get() {
|
||||
val activity = activity
|
||||
return if (activity is MainActivity) {
|
||||
activity
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
get() = activity as? MainActivity
|
||||
|
||||
fun hideSoftInput() = activity?.window?.decorView?.run { SoftwareKeyboardControllerCompat(this) }?.hide()
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.databinding.SceneToolbarBinding
|
||||
|
||||
abstract class BaseToolbarScene : BaseScene(), ToolBarScene {
|
||||
@@ -61,12 +60,7 @@ abstract class BaseToolbarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
return if (item.itemId == R.id.action_open_side_sheet) {
|
||||
openSideSheet()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun onNavigationClick() {
|
||||
|
||||
@@ -53,8 +53,6 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
@@ -145,6 +143,9 @@ class DownloadsScene :
|
||||
private val dialogState = DialogState()
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
override val fastScroller get() = binding.fastScroller
|
||||
override val recyclerView get() = binding.recyclerView
|
||||
override val contentView get() = binding.content
|
||||
|
||||
private fun initLabels() {
|
||||
context ?: return
|
||||
@@ -267,12 +268,10 @@ class DownloadsScene :
|
||||
).apply {
|
||||
addCustomChoiceListener({
|
||||
binding.fabLayout.isExpanded = true
|
||||
// Lock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
lockDrawer()
|
||||
}) {
|
||||
binding.fabLayout.isExpanded = false
|
||||
// Unlock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
unlockDrawer()
|
||||
}
|
||||
restoreSelection(savedInstanceState)
|
||||
}
|
||||
@@ -346,7 +345,6 @@ class DownloadsScene :
|
||||
fabLayout.setHidePrimaryFab(true)
|
||||
fabLayout.setAutoCancel(false)
|
||||
fabLayout.setOnClickFabListener(this@DownloadsScene)
|
||||
addAboveSnackView(fabLayout)
|
||||
updateForLabel()
|
||||
}
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
@@ -369,7 +367,6 @@ class DownloadsScene :
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
binding.recyclerView.stopScroll()
|
||||
removeAboveSnackView(binding.fabLayout)
|
||||
mViewTransition = null
|
||||
mAdapter = null
|
||||
mLabelAdapter = null
|
||||
@@ -378,7 +375,7 @@ class DownloadsScene :
|
||||
}
|
||||
|
||||
override fun onNavigationClick() {
|
||||
toggleDrawer(GravityCompat.START)
|
||||
openDrawer()
|
||||
}
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
|
||||
@@ -33,14 +33,11 @@ import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.currentRecomposeScope
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsAnimationCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -88,6 +85,7 @@ import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
@@ -95,6 +93,7 @@ import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
@@ -103,7 +102,6 @@ import rikka.core.res.resolveColor
|
||||
// Note that we do not really follow mvvm structure, just use it as ... storage
|
||||
class VMStorage : ViewModel() {
|
||||
var urlBuilder = FavListUrlBuilder(favCat = Settings.recentFavCat)
|
||||
var shouldScrollToTop = false
|
||||
private val cloudDataFlow = Pager(PagingConfig(25)) {
|
||||
object : PagingSource<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
@@ -120,7 +118,6 @@ class VMStorage : ViewModel() {
|
||||
} else {
|
||||
urlBuilder.setIndex(key, false)
|
||||
}
|
||||
shouldScrollToTop = true
|
||||
}
|
||||
}
|
||||
val r = runSuspendCatching {
|
||||
@@ -156,19 +153,14 @@ class FavoritesScene : SearchBarScene() {
|
||||
private val binding get() = _binding!!
|
||||
private var mAdapter: GalleryAdapter? = null
|
||||
private val tracker get() = mAdapter!!.tracker!!
|
||||
private val showNormalFabsRunnable = Runnable {
|
||||
updateJumpFab() // index: 0, 2
|
||||
binding.fabLayout.run {
|
||||
setSecondaryFabVisibilityAt(1, true)
|
||||
for (i in 3..6) {
|
||||
setSecondaryFabVisibilityAt(i, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
private var showNormalFabsJob: Job? = null
|
||||
|
||||
private val dialogState = DialogState()
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
override val fastScroller get() = binding.fastScroller
|
||||
override val recyclerView get() = binding.recyclerView
|
||||
override val contentView get() = binding.contentLayout.contentView
|
||||
|
||||
private fun onItemClick(position: Int) {
|
||||
// Skip if in search mode
|
||||
@@ -233,7 +225,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
_binding = SceneFavoritesBinding.inflate(inflater, container!!)
|
||||
container.addView(ComposeView(inflater.context).apply { setMD3Content { dialogState.Intercept() } })
|
||||
container.addView(ComposeWithViewLifecycle().apply { setMD3Content { dialogState.Intercept() } })
|
||||
setOnApplySearch {
|
||||
if (!tracker.isInCustomChoice) {
|
||||
switchFav(urlBuilder.favCat, it)
|
||||
@@ -309,7 +301,6 @@ class FavoritesScene : SearchBarScene() {
|
||||
if (tracker.isInCustomChoice) tracker.clearSelection()
|
||||
}
|
||||
}
|
||||
addAboveSnackView(this)
|
||||
}
|
||||
binding.fastScroller.setOnDragHandlerListener(object : OnDragHandlerListener {
|
||||
override fun onStartDragHandler() {}
|
||||
@@ -336,6 +327,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
binding.tip.setCompoundDrawables(null, drawable, null, null)
|
||||
binding.tip.setOnClickListener { mAdapter?.retry() }
|
||||
binding.refreshLayout.setProgressViewOffset(true, 0, 64.dpToPx)
|
||||
binding.refreshLayout.setOnRefreshListener { switchFav(urlBuilder.favCat) }
|
||||
val transition = ViewTransition(binding.refreshLayout, binding.progress, binding.tip)
|
||||
val empty = getString(R.string.gallery_list_empty_hit)
|
||||
@@ -345,10 +337,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
is LoadState.Loading -> {
|
||||
showSearchBar()
|
||||
if (!binding.refreshLayout.isRefreshing) {
|
||||
// https://github.com/FooIbar/EhViewer/issues/45
|
||||
// transition.showView(1)
|
||||
transition.showView(0, false)
|
||||
binding.refreshLayout.isRefreshing = true
|
||||
transition.showView(1)
|
||||
}
|
||||
}
|
||||
is LoadState.Error -> {
|
||||
@@ -362,11 +351,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
binding.tip.text = empty
|
||||
transition.showView(2)
|
||||
} else {
|
||||
transition.showView(0, false)
|
||||
if (vm.shouldScrollToTop) {
|
||||
vm.shouldScrollToTop = false
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
}
|
||||
transition.showView(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,15 +369,13 @@ class FavoritesScene : SearchBarScene() {
|
||||
// Delay expanding action to make layout work fine
|
||||
SimpleHandler.post { binding.fabLayout.isExpanded = true }
|
||||
binding.refreshLayout.isEnabled = false
|
||||
// Lock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
lockDrawer()
|
||||
}) {
|
||||
showNormalFab()
|
||||
binding.fabLayout.setAutoCancel(true)
|
||||
binding.fabLayout.isExpanded = false
|
||||
binding.refreshLayout.isEnabled = true
|
||||
// Unlock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
unlockDrawer()
|
||||
}
|
||||
restoreSelection(savedInstanceState)
|
||||
}
|
||||
@@ -451,12 +434,12 @@ class FavoritesScene : SearchBarScene() {
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
binding.recyclerView.stopScroll()
|
||||
removeAboveSnackView(binding.fabLayout)
|
||||
mAdapter = null
|
||||
_binding = null
|
||||
showNormalFabsJob = null
|
||||
}
|
||||
|
||||
override fun onCreateDrawerView(inflater: LayoutInflater) = ComposeView(inflater.context).apply {
|
||||
override fun onCreateDrawerView(inflater: LayoutInflater) = ComposeWithViewLifecycle().apply {
|
||||
setMD3Content {
|
||||
val localFavCount by vm.localFavCount.collectAsState(0)
|
||||
ElevatedCard {
|
||||
@@ -532,12 +515,20 @@ class FavoritesScene : SearchBarScene() {
|
||||
|
||||
private fun showNormalFab() {
|
||||
// Delay showing normal fab to avoid mutation
|
||||
SimpleHandler.removeCallbacks(showNormalFabsRunnable)
|
||||
SimpleHandler.postDelayed(showNormalFabsRunnable, 300)
|
||||
showNormalFabsJob = viewLifecycleOwner.lifecycleScope.launch {
|
||||
delay(300)
|
||||
updateJumpFab() // index: 0, 2
|
||||
binding.fabLayout.run {
|
||||
setSecondaryFabVisibilityAt(1, true)
|
||||
for (i in 3..6) {
|
||||
setSecondaryFabVisibilityAt(i, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSelectionFab() {
|
||||
SimpleHandler.removeCallbacks(showNormalFabsRunnable)
|
||||
showNormalFabsJob?.cancel()
|
||||
binding.fabLayout.run {
|
||||
for (i in 0..2) {
|
||||
setSecondaryFabVisibilityAt(i, false)
|
||||
|
||||
@@ -87,8 +87,10 @@ import com.hippo.ehviewer.util.ReadableTime
|
||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
||||
import com.hippo.ehviewer.util.TextUrl
|
||||
import com.hippo.ehviewer.util.addTextToClipboard
|
||||
import com.hippo.ehviewer.util.applyNavigationBarsPadding
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import com.hippo.ehviewer.util.toBBCode
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlin.math.hypot
|
||||
@@ -256,10 +258,19 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
|
||||
}
|
||||
}
|
||||
binding.fab.setOnClickListener(this)
|
||||
addAboveSnackView(binding.editPanel)
|
||||
addAboveSnackView(binding.fabLayout)
|
||||
mViewTransition = ViewTransition(binding.refreshLayout, tip)
|
||||
updateView(false)
|
||||
binding.editPanel.applyInsetter {
|
||||
type(ime = true, navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
binding.recyclerView.applyInsetter {
|
||||
type(ime = true, navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
binding.fabLayout.applyNavigationBarsPadding()
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -282,8 +293,6 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
|
||||
super.onDestroyView()
|
||||
callback.remove()
|
||||
binding.recyclerView.stopScroll()
|
||||
removeAboveSnackView(binding.editPanel)
|
||||
removeAboveSnackView(binding.fabLayout)
|
||||
mAdapter = null
|
||||
mViewTransition = null
|
||||
_binding = null
|
||||
@@ -428,7 +437,7 @@ class GalleryCommentsScene : BaseToolbarScene(), View.OnClickListener, OnRefresh
|
||||
return@setItems
|
||||
}
|
||||
when (menuId[which]) {
|
||||
R.id.copy -> requireActivity().addTextToClipboard(text, false)
|
||||
R.id.copy -> requireActivity().addTextToClipboard(text)
|
||||
R.id.block_commenter -> showFilterCommenterDialog(comment.user, position)
|
||||
R.id.vote_up -> voteComment(comment.id, 1)
|
||||
R.id.vote_down -> voteComment(comment.id, -1)
|
||||
|
||||
@@ -91,12 +91,11 @@ import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.layout.LocalPinnableContainer
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.viewinterop.AndroidViewBinding
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -416,7 +415,7 @@ class GalleryDetailScene : BaseScene() {
|
||||
getDetailError = getString(R.string.error_cannot_find_gallery)
|
||||
}
|
||||
(requireActivity() as MainActivity).mShareUrl = galleryDetailUrl
|
||||
return ComposeView(requireContext()).apply {
|
||||
return ComposeWithViewLifecycle().apply {
|
||||
setMD3Content {
|
||||
LaunchedEffect(gid) {
|
||||
EhDownloadManager.stateFlow(gid).collect {
|
||||
@@ -726,7 +725,9 @@ class GalleryDetailScene : BaseScene() {
|
||||
Spacer(modifier = Modifier.size(dimensionResource(id = R.dimen.keyline_margin)))
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp, Alignment.CenterHorizontally),
|
||||
) {
|
||||
val favored by produceState(initialValue = false) {
|
||||
@@ -979,7 +980,7 @@ class GalleryDetailScene : BaseScene() {
|
||||
context ?: return
|
||||
if (mPage != 0) {
|
||||
Snackbar.make(
|
||||
requireActivity().findViewById(R.id.snackbar),
|
||||
requireActivity().findViewById(R.id.fragment_container),
|
||||
getString(R.string.read_from, mPage),
|
||||
Snackbar.LENGTH_LONG,
|
||||
).setAction(R.string.read) { context?.navToReader(gd.galleryInfo, mPage) }.show()
|
||||
@@ -1014,9 +1015,9 @@ class GalleryDetailScene : BaseScene() {
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
@Composable
|
||||
private fun GalleryDetailComment(commentsList: Array<GalleryComment>?) {
|
||||
private fun GalleryDetailComment(commentsList: Array<GalleryComment>) {
|
||||
val maxShowCount = 2
|
||||
val commentText = if (commentsList.isNullOrEmpty()) {
|
||||
val commentText = if (commentsList.isEmpty()) {
|
||||
stringResource(R.string.no_comments)
|
||||
} else if (commentsList.size <= maxShowCount) {
|
||||
stringResource(R.string.no_more_comments)
|
||||
@@ -1024,25 +1025,24 @@ class GalleryDetailScene : BaseScene() {
|
||||
stringResource(R.string.more_comment)
|
||||
}
|
||||
CrystalCard {
|
||||
if (commentsList != null) {
|
||||
val length = maxShowCount.coerceAtMost(commentsList.size)
|
||||
for (i in 0 until length) {
|
||||
val comment = commentsList[i]
|
||||
AndroidView(factory = { context ->
|
||||
ItemGalleryCommentBinding.inflate(LayoutInflater.from(context), null, false).apply {
|
||||
card.setOnClickListener { onNavigateToCommentScene() }
|
||||
user.text = comment.user
|
||||
user.setBackgroundColor(Color.TRANSPARENT)
|
||||
time.text = ReadableTime.getTimeAgo(comment.time)
|
||||
this.comment.maxLines = 5
|
||||
this.comment.text = comment.comment.orEmpty().parseAsHtml(imageGetter = CoilImageGetter(this.comment))
|
||||
}.root
|
||||
})
|
||||
val length = maxShowCount.coerceAtMost(commentsList.size)
|
||||
for (i in 0 until length) {
|
||||
val item = commentsList[i]
|
||||
AndroidViewBinding(factory = ItemGalleryCommentBinding::inflate) {
|
||||
card.setOnClickListener { onNavigateToCommentScene() }
|
||||
user.text = item.user
|
||||
user.setBackgroundColor(Color.TRANSPARENT)
|
||||
time.text = ReadableTime.getTimeAgo(item.time)
|
||||
comment.maxLines = 5
|
||||
comment.text = item.comment.orEmpty().parseAsHtml(imageGetter = CoilImageGetter(comment))
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = dimensionResource(id = R.dimen.strip_item_padding_v))
|
||||
.clip(RoundedCornerShape(16.dp)).clickable(onClick = ::onNavigateToCommentScene),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = dimensionResource(id = R.dimen.strip_item_padding_v))
|
||||
.clip(RoundedCornerShape(16.dp))
|
||||
.clickable(onClick = ::onNavigateToCommentScene),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Text(commentText)
|
||||
@@ -1211,11 +1211,11 @@ class GalleryDetailScene : BaseScene() {
|
||||
}
|
||||
|
||||
R.id.copy -> {
|
||||
requireActivity().addTextToClipboard(tag, false)
|
||||
requireActivity().addTextToClipboard(tag)
|
||||
}
|
||||
|
||||
R.id.copy_trans -> {
|
||||
requireActivity().addTextToClipboard(translated, false)
|
||||
requireActivity().addTextToClipboard(translated)
|
||||
}
|
||||
}
|
||||
}.show()
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.animation.Animator
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
@@ -29,14 +30,11 @@ import android.widget.TextView
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsAnimationCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -72,7 +70,6 @@ import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_NORMAL
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_SUBSCRIPTION
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TAG
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TOPLIST
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_UPLOADER
|
||||
import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_WHATS_HOT
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import com.hippo.ehviewer.client.exception.EhException
|
||||
@@ -101,13 +98,17 @@ import com.hippo.ehviewer.ui.tools.DialogState
|
||||
import com.hippo.ehviewer.updater.AppUpdater
|
||||
import com.hippo.ehviewer.util.AnimationUtils
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
||||
import com.hippo.ehviewer.util.applyNavigationBarsPadding
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import com.hippo.ehviewer.util.getValue
|
||||
import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import java.io.File
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
@@ -117,6 +118,7 @@ import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import moe.tarsin.coroutines.runSuspendCatching
|
||||
@@ -124,7 +126,6 @@ import rikka.core.res.resolveColor
|
||||
|
||||
class VMStorage1 : ViewModel() {
|
||||
var urlBuilder = ListUrlBuilder()
|
||||
var shouldScrollToTop = false
|
||||
val dataFlow = Pager(PagingConfig(25)) {
|
||||
object : PagingSource<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
@@ -155,7 +156,6 @@ class VMStorage1 : ViewModel() {
|
||||
} else {
|
||||
urlBuilder.setIndex(key, false)
|
||||
}
|
||||
shouldScrollToTop = true
|
||||
}
|
||||
}
|
||||
val r = runSuspendCatching {
|
||||
@@ -201,8 +201,16 @@ class GalleryListScene : SearchBarScene() {
|
||||
lateinit var mQuickSearchList: MutableList<QuickSearch>
|
||||
private var mHideActionFabSlop = 0
|
||||
private var mState = State.NORMAL
|
||||
private val mSearchFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
binding.searchFab.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override val fabLayout get() = binding.fabLayout
|
||||
override val fastScroller get() = binding.fastScroller
|
||||
override val recyclerView get() = binding.recyclerView
|
||||
override val contentView get() = binding.contentLayout.contentView
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
return R.menu.scene_gallery_list_searchbar_menu
|
||||
@@ -214,7 +222,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
ACTION_HOMEPAGE -> ListUrlBuilder()
|
||||
ACTION_SUBSCRIPTION -> ListUrlBuilder(MODE_SUBSCRIPTION)
|
||||
ACTION_WHATS_HOT -> ListUrlBuilder(MODE_WHATS_HOT)
|
||||
ACTION_TOP_LIST -> ListUrlBuilder(MODE_TOPLIST, mKeyword = "11")
|
||||
ACTION_TOP_LIST -> ListUrlBuilder(MODE_TOPLIST, mKeyword = Settings.recentToplist)
|
||||
ACTION_LIST_URL_BUILDER -> args?.getParcelableCompat<ListUrlBuilder>(KEY_LIST_URL_BUILDER)?.copy() ?: ListUrlBuilder()
|
||||
else -> throw IllegalStateException("Wrong KEY_ACTION:${args?.getString(KEY_ACTION)} when handle args!")
|
||||
}
|
||||
@@ -286,14 +294,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
title = resources.getString(R.string.search)
|
||||
}
|
||||
setSearchBarHint(title)
|
||||
|
||||
when (mode) {
|
||||
MODE_NORMAL, MODE_SUBSCRIPTION -> if (category != EhUtils.NONE || !keyword.isNullOrEmpty()) {
|
||||
mainActivity?.clearNavCheckedItem()
|
||||
}
|
||||
MODE_TAG, MODE_UPLOADER, MODE_IMAGE_SEARCH -> mainActivity?.clearNavCheckedItem()
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
private val dialogState = DialogState()
|
||||
@@ -321,7 +321,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
savedInstanceState: Bundle?,
|
||||
): View {
|
||||
_binding = SceneGalleryListBinding.inflate(inflater, container!!)
|
||||
container.addView(ComposeView(inflater.context).apply { setMD3Content { dialogState.Intercept() } })
|
||||
container.addView(ComposeWithViewLifecycle().apply { setMD3Content { dialogState.Intercept() } })
|
||||
checkForUpdates()
|
||||
requireActivity().onBackPressedDispatcher.addCallback(stateBackPressedCallback)
|
||||
mHideActionFabSlop = ViewConfiguration.get(requireContext()).scaledTouchSlop
|
||||
@@ -331,7 +331,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
WindowInsetsAnimationHelper(
|
||||
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP,
|
||||
binding.fabLayout,
|
||||
mSearchFab.parent as View,
|
||||
binding.searchFab.parent as View,
|
||||
),
|
||||
)
|
||||
mViewTransition = BringOutTransition(binding.contentLayout.contentView, binding.searchLayout)
|
||||
@@ -366,6 +366,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
|
||||
binding.tip.setCompoundDrawables(null, drawable, null, null)
|
||||
binding.tip.setOnClickListener { mAdapter?.retry() }
|
||||
binding.refreshLayout.setProgressViewOffset(true, 0, 64.dpToPx)
|
||||
binding.refreshLayout.setOnRefreshListener {
|
||||
mUrlBuilder.setIndex(null, true)
|
||||
mUrlBuilder.mJumpTo = null
|
||||
@@ -379,12 +380,10 @@ class GalleryListScene : SearchBarScene() {
|
||||
is LoadState.Loading -> {
|
||||
showSearchBar()
|
||||
if (!binding.refreshLayout.isRefreshing) {
|
||||
// https://github.com/FooIbar/EhViewer/issues/45
|
||||
// transition.showView(1)
|
||||
transition.showView(0, false)
|
||||
binding.refreshLayout.isRefreshing = true
|
||||
transition.showView(1)
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.Error -> {
|
||||
binding.refreshLayout.isRefreshing = false
|
||||
binding.tip.text = ExceptionUtils.getReadableString(state.error)
|
||||
@@ -400,22 +399,20 @@ class GalleryListScene : SearchBarScene() {
|
||||
navAnimated(R.id.webView, bundleOf(WebViewActivity.KEY_URL to EhUrl.host))
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.NotLoading -> {
|
||||
binding.refreshLayout.isRefreshing = false
|
||||
if (mAdapter?.itemCount == 0) {
|
||||
binding.tip.text = empty
|
||||
transition.showView(2)
|
||||
} else {
|
||||
transition.showView(0, false)
|
||||
if (vm.shouldScrollToTop) {
|
||||
vm.shouldScrollToTop = false
|
||||
binding.recyclerView.scrollToPosition(0)
|
||||
}
|
||||
transition.showView(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Settings.needSignInFlow.first { !it }
|
||||
vm.dataFlow.collectLatest {
|
||||
adapter.submitData(it)
|
||||
}
|
||||
@@ -463,6 +460,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
mUrlBuilder.mJumpTo = null
|
||||
mAdapter?.refresh()
|
||||
}
|
||||
|
||||
2 -> { // Last
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.mJumpTo = "${TOPLIST_PAGES - 1}"
|
||||
@@ -480,15 +478,24 @@ class GalleryListScene : SearchBarScene() {
|
||||
val actionFabDrawable = AddDeleteDrawable(requireContext(), colorID)
|
||||
binding.fabLayout.addOnExpandListener {
|
||||
if (it) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
lockDrawer()
|
||||
actionFabDrawable.setDelete(ANIMATE_TIME)
|
||||
} else {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
unlockDrawer()
|
||||
actionFabDrawable.setAdd(ANIMATE_TIME)
|
||||
}
|
||||
}
|
||||
binding.fabLayout.primaryFab!!.setImageDrawable(actionFabDrawable)
|
||||
addAboveSnackView(binding.fabLayout)
|
||||
binding.searchFab.setOnClickListener { onApplySearch() }
|
||||
(binding.searchFab.parent as View).apply {
|
||||
bringToFront()
|
||||
applyNavigationBarsPadding()
|
||||
}
|
||||
binding.searchLayout.applyInsetter {
|
||||
type(statusBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
|
||||
// Update list url builder
|
||||
onUpdateUrlBuilder()
|
||||
@@ -504,7 +511,6 @@ class GalleryListScene : SearchBarScene() {
|
||||
super.onDestroyView()
|
||||
stateBackPressedCallback.remove()
|
||||
binding.recyclerView.stopScroll()
|
||||
removeAboveSnackView(binding.fabLayout)
|
||||
_binding = null
|
||||
fabAnimator = null
|
||||
mAdapter = null
|
||||
@@ -705,6 +711,47 @@ class GalleryListScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun showSearchFab(animation: Boolean, delay: Long = 0) {
|
||||
if (animation) {
|
||||
binding.searchFab.visibility = View.VISIBLE
|
||||
binding.searchFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
binding.searchFab.visibility = View.VISIBLE
|
||||
binding.searchFab.scaleX = 1.0f
|
||||
binding.searchFab.scaleY = 1.0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideSearchFab(animation: Boolean): Long {
|
||||
return if (View.INVISIBLE == binding.searchFab.visibility) {
|
||||
0L
|
||||
} else {
|
||||
if (animation) {
|
||||
binding.searchFab.animate().scaleX(0.0f).scaleY(0.0f)
|
||||
.setListener(mSearchFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
} else {
|
||||
binding.searchFab.visibility = View.INVISIBLE
|
||||
binding.searchFab.scaleX = 0.0f
|
||||
binding.searchFab.scaleY = 0.0f
|
||||
}
|
||||
ANIMATE_TIME
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectSearchFab(animation: Boolean) {
|
||||
val delay = hideActionFab(animation)
|
||||
showSearchFab(animation, delay)
|
||||
}
|
||||
|
||||
private fun selectActionFab(animation: Boolean) {
|
||||
val delay = hideSearchFab(animation)
|
||||
showActionFab(animation, delay)
|
||||
}
|
||||
|
||||
override fun onSearchViewExpanded() {
|
||||
if (mState == State.NORMAL) {
|
||||
hideActionFab(true)
|
||||
@@ -854,9 +901,9 @@ class GalleryListScene : SearchBarScene() {
|
||||
private fun onStateChange(newState: State) {
|
||||
stateBackPressedCallback.isEnabled = newState != State.NORMAL
|
||||
if (newState == State.NORMAL || newState == State.SIMPLE_SEARCH) {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
unlockDrawer()
|
||||
} else {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
lockDrawer()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -864,7 +911,8 @@ class GalleryListScene : SearchBarScene() {
|
||||
|
||||
private inner class QsDrawerAdapter(private val mInflater: LayoutInflater) :
|
||||
RecyclerView.Adapter<QsDrawerHolder>() {
|
||||
private val keywords = intArrayOf(11, 12, 13, 15)
|
||||
private val toplists = resources.getStringArray(R.array.toplist_entries)
|
||||
private val keywords = resources.getStringArray(R.array.toplist_values)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QsDrawerHolder {
|
||||
val holder = QsDrawerHolder(ItemDrawerListBinding.inflate(mInflater, parent, false))
|
||||
@@ -874,7 +922,9 @@ class GalleryListScene : SearchBarScene() {
|
||||
navAnimated(R.id.galleryListScene, ListUrlBuilder().apply { set(q) }.toStartArgs())
|
||||
} else {
|
||||
if (mIsTopList) {
|
||||
mUrlBuilder.keyword = keywords[holder.bindingAdapterPosition].toString()
|
||||
val keyword = keywords[holder.bindingAdapterPosition]
|
||||
Settings.recentToplist = keyword
|
||||
mUrlBuilder.keyword = keyword
|
||||
mUrlBuilder.mJumpTo = null
|
||||
} else {
|
||||
val q = mQuickSearchList[holder.bindingAdapterPosition]
|
||||
@@ -894,13 +944,7 @@ class GalleryListScene : SearchBarScene() {
|
||||
if (!mIsTopList) {
|
||||
text.text = mQuickSearchList[position].name
|
||||
} else {
|
||||
val toplists = intArrayOf(
|
||||
R.string.toplist_alltime,
|
||||
R.string.toplist_pastyear,
|
||||
R.string.toplist_pastmonth,
|
||||
R.string.toplist_yesterday,
|
||||
)
|
||||
text.text = getString(toplists[position])
|
||||
text.text = toplists[position]
|
||||
option.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.padding
|
||||
@@ -53,6 +54,7 @@ import com.hippo.ehviewer.client.data.GalleryDetail
|
||||
import com.hippo.ehviewer.client.data.GalleryPreview
|
||||
import com.hippo.ehviewer.coil.justDownload
|
||||
import com.hippo.ehviewer.ktbuilder.imageRequest
|
||||
import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.legacy.calculateSuitableSpanCount
|
||||
import com.hippo.ehviewer.ui.main.EhPreviewItem
|
||||
import com.hippo.ehviewer.ui.navToReader
|
||||
@@ -60,6 +62,7 @@ import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
|
||||
import com.hippo.ehviewer.ui.tools.rememberDialogState
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
import com.hippo.ehviewer.util.findActivity
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -158,6 +161,11 @@ class GalleryPreviewScreen : Fragment() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
requireContext().findActivity<MainActivity>().recompose()
|
||||
}
|
||||
}
|
||||
|
||||
const val KEY_GALLERY_DETAIL = "gallery_detail"
|
||||
|
||||
@@ -44,13 +44,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
@@ -61,6 +58,8 @@ import androidx.paging.compose.itemKey
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings.listThumbSize
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.big.History
|
||||
import com.hippo.ehviewer.ui.doGalleryInfoAction
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
@@ -73,9 +72,10 @@ import eu.kanade.tachiyomi.util.system.pxToDp
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class HistoryScene : BaseScene() {
|
||||
override val showLeftDrawer = true
|
||||
// Disabled for breaking swipe-to-dismiss
|
||||
override val enableDrawerGestures = false
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = ComposeView(inflater.context).apply {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) = ComposeWithViewLifecycle().apply {
|
||||
setMD3Content {
|
||||
val dialogState = rememberDialogState()
|
||||
dialogState.Intercept()
|
||||
@@ -88,7 +88,7 @@ class HistoryScene : BaseScene() {
|
||||
TopAppBar(
|
||||
title = { Text(text = stringResource(id = R.string.history)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { toggleDrawer(GravityCompat.START) }) {
|
||||
IconButton(onClick = { openDrawer() }) {
|
||||
Icon(imageVector = Icons.Default.Menu, contentDescription = null)
|
||||
}
|
||||
},
|
||||
@@ -161,19 +161,25 @@ class HistoryScene : BaseScene() {
|
||||
directions = setOf(DismissDirection.EndToStart),
|
||||
)
|
||||
} else {
|
||||
CrystalCard(modifier = Modifier.height(cardHeight).fillMaxWidth()) {}
|
||||
CrystalCard(
|
||||
modifier = Modifier
|
||||
.height(cardHeight)
|
||||
.fillMaxWidth(),
|
||||
) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Deferred({ delay(200) }) {
|
||||
if (historyData.itemCount == 0) {
|
||||
Column(
|
||||
modifier = Modifier.padding(paddingValues).fillMaxSize(),
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.big_history),
|
||||
imageVector = EhIcons.Big.Default.History,
|
||||
contentDescription = null,
|
||||
Modifier.padding(16.dp),
|
||||
)
|
||||
|
||||
@@ -20,14 +20,14 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.big.SadAndroid
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
@@ -35,7 +35,7 @@ import moe.tarsin.coroutines.runSuspendCatching
|
||||
|
||||
class ProgressScene : BaseScene() {
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
return ComposeView(inflater.context).apply {
|
||||
return ComposeWithViewLifecycle().apply {
|
||||
setMD3Content {
|
||||
val action = rememberSaveable { requireArguments().getString(KEY_ACTION, INVALID) }
|
||||
val gid = rememberSaveable { requireArguments().getLong(KEY_GID, -1) }
|
||||
@@ -77,7 +77,7 @@ class ProgressScene : BaseScene() {
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.big_sad_pandroid),
|
||||
imageVector = EhIcons.Big.Default.SadAndroid,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
/**
|
||||
* compose view with fragment view lifecycle
|
||||
* use it after [Fragment.onCreateView]
|
||||
* also @see [Fragment.getViewLifecycleOwner]
|
||||
*/
|
||||
@Suppress("FunctionName")
|
||||
fun Fragment.ComposeWithViewLifecycle() = ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner))
|
||||
}
|
||||
@@ -9,10 +9,9 @@ import android.view.ViewGroup
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.navigationBars
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -28,9 +27,8 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.core.widget.addTextChangedListener
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.search.SearchView
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
@@ -46,8 +44,10 @@ import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
import com.hippo.ehviewer.util.AnimationUtils
|
||||
import com.hippo.ehviewer.util.SimpleAnimatorListener
|
||||
import com.hippo.ehviewer.util.applyNavigationBarsPadding
|
||||
import com.jamal.composeprefs3.ui.ifNotNullThen
|
||||
import com.jamal.composeprefs3.ui.ifTrueThen
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@@ -58,24 +58,22 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
private val binding get() = _binding!!
|
||||
private var mSuggestionList by mutableStateOf(emptyList<Suggestion>())
|
||||
private var mSuggestionProvider: SuggestionProvider? = null
|
||||
var allowEmptySearch = true
|
||||
private var allowEmptySearch = true
|
||||
private val mSearchDatabase = searchDatabase.searchDao()
|
||||
private var onApplySearch: (String) -> Unit = {}
|
||||
protected val mSearchFab get() = binding.searchFab
|
||||
protected abstract val fabLayout: FabLayout
|
||||
protected var mShowActionFab = true
|
||||
protected val mActionFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
fabLayout.primaryFab?.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
private val mSearchFabAnimatorListener = object : SimpleAnimatorListener() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
override val showLeftDrawer = true
|
||||
protected abstract val fabLayout: FabLayout
|
||||
protected abstract val fastScroller: View
|
||||
protected abstract val recyclerView: View
|
||||
protected abstract val contentView: View
|
||||
|
||||
override val enableDrawerGestures = true
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
|
||||
_binding = SceneSearchbarBinding.inflate(inflater, container, false)
|
||||
@@ -86,8 +84,9 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
true
|
||||
}
|
||||
binding.searchBarList.consumeWindowInsets = false
|
||||
binding.searchBarList.setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnLifecycleDestroyed(viewLifecycleOwner))
|
||||
binding.searchBarList.setMD3Content {
|
||||
LazyColumn(contentPadding = WindowInsets.navigationBars.asPaddingValues()) {
|
||||
LazyColumn(modifier = Modifier.navigationBarsPadding().imePadding()) {
|
||||
items(mSuggestionList) {
|
||||
ListItem(
|
||||
headlineContent = { Text(text = it.keyword) },
|
||||
@@ -119,13 +118,20 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
onSearchViewHidden()
|
||||
}
|
||||
}
|
||||
mSearchFab.setOnClickListener { onApplySearch() }
|
||||
onCreateViewWithToolbar(inflater, binding.root, savedInstanceState)
|
||||
// This has to be placed after onCreateViewWithToolbar() since
|
||||
// callbacks are invoked in the reverse order in which they are added
|
||||
binding.searchview.addTransitionListener(mSearchViewOnBackPressedCallback)
|
||||
requireActivity().onBackPressedDispatcher.addCallback(mSearchViewOnBackPressedCallback)
|
||||
binding.appbar.bringToFront()
|
||||
fabLayout.applyNavigationBarsPadding()
|
||||
fastScroller.applyNavigationBarsPadding()
|
||||
recyclerView.applyNavigationBarsPadding()
|
||||
contentView.applyInsetter {
|
||||
type(statusBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
return binding.root
|
||||
}
|
||||
|
||||
@@ -148,16 +154,13 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
_binding = null
|
||||
}
|
||||
|
||||
private var privLockModeStart: Int? = null
|
||||
private var drawerLocked = false
|
||||
|
||||
@CallSuper
|
||||
open fun onSearchViewExpanded() {
|
||||
privLockModeStart = getDrawerLockMode(GravityCompat.START)
|
||||
privLockModeStart?.let {
|
||||
setDrawerLockMode(
|
||||
DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
|
||||
GravityCompat.START,
|
||||
)
|
||||
if (!isDrawerLocked) {
|
||||
drawerLocked = true
|
||||
lockDrawer()
|
||||
}
|
||||
updateSuggestions()
|
||||
}
|
||||
@@ -165,38 +168,9 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
@CallSuper
|
||||
open fun onSearchViewHidden() {
|
||||
binding.toolbar.setText(binding.searchview.text)
|
||||
privLockModeStart?.let { setDrawerLockMode(it, GravityCompat.START) }
|
||||
privLockModeStart = null
|
||||
}
|
||||
|
||||
fun showSearchFab(animation: Boolean, delay: Long = 0) {
|
||||
if (animation) {
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.animate().scaleX(1.0f).scaleY(1.0f).setListener(null)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(delay)
|
||||
.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR).start()
|
||||
} else {
|
||||
mSearchFab.visibility = View.VISIBLE
|
||||
mSearchFab.scaleX = 1.0f
|
||||
mSearchFab.scaleY = 1.0f
|
||||
}
|
||||
}
|
||||
|
||||
fun hideSearchFab(animation: Boolean): Long {
|
||||
return if (View.INVISIBLE == mSearchFab.visibility) {
|
||||
0L
|
||||
} else {
|
||||
if (animation) {
|
||||
mSearchFab.animate().scaleX(0.0f).scaleY(0.0f)
|
||||
.setListener(mSearchFabAnimatorListener)
|
||||
.setDuration(ANIMATE_TIME).setStartDelay(0L)
|
||||
.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR).start()
|
||||
} else {
|
||||
mSearchFab.visibility = View.INVISIBLE
|
||||
mSearchFab.scaleX = 0.0f
|
||||
mSearchFab.scaleY = 0.0f
|
||||
}
|
||||
ANIMATE_TIME
|
||||
if (drawerLocked) {
|
||||
unlockDrawer()
|
||||
drawerLocked = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,16 +214,6 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
}
|
||||
|
||||
fun selectSearchFab(animation: Boolean) {
|
||||
val delay = hideActionFab(animation)
|
||||
showSearchFab(animation, delay)
|
||||
}
|
||||
|
||||
fun selectActionFab(animation: Boolean) {
|
||||
val delay = hideSearchFab(animation)
|
||||
showActionFab(animation, delay)
|
||||
}
|
||||
|
||||
fun setSearchBarHint(hint: String?) {
|
||||
binding.toolbar.hint = hint
|
||||
}
|
||||
@@ -264,7 +228,7 @@ abstract class SearchBarScene : BaseScene(), ToolBarScene {
|
||||
}
|
||||
|
||||
override fun onNavigationClick() {
|
||||
toggleDrawer(GravityCompat.START)
|
||||
openDrawer()
|
||||
}
|
||||
|
||||
override fun getMenuResId(): Int {
|
||||
|
||||
@@ -21,8 +21,10 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -31,11 +33,13 @@ import com.hippo.ehviewer.BuildConfig
|
||||
import com.hippo.ehviewer.EhDB
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.Settings
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.EhEngine
|
||||
import com.hippo.ehviewer.client.data.FavListUrlBuilder
|
||||
import com.hippo.ehviewer.ui.LocalNavController
|
||||
import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.ui.tools.rememberDialogState
|
||||
import com.hippo.ehviewer.util.AppConfig
|
||||
import com.hippo.ehviewer.util.ReadableTime
|
||||
import java.io.File
|
||||
@@ -54,6 +58,8 @@ fun AdvancedScreen() {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope { Dispatchers.IO }
|
||||
fun launchSnackBar(content: String) = coroutineScope.launch { snackbarHostState.showSnackbar(content) }
|
||||
val dialogState = rememberDialogState()
|
||||
dialogState.Intercept()
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
@@ -133,6 +139,19 @@ fun AdvancedScreen() {
|
||||
title = stringResource(id = R.string.preload_thumb_aggressively),
|
||||
value = Settings::preloadThumbAggressively,
|
||||
)
|
||||
var userAgent by Settings::userAgent.observed
|
||||
val userAgentTitle = stringResource(id = R.string.user_agent)
|
||||
Preference(
|
||||
title = userAgentTitle,
|
||||
summary = userAgent,
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
userAgent = dialogState.awaitInputText(
|
||||
initial = userAgent,
|
||||
title = userAgentTitle,
|
||||
).trim().takeUnless { it.isBlank() } ?: CHROME_USER_AGENT
|
||||
}
|
||||
}
|
||||
val exportFailed = stringResource(id = R.string.settings_advanced_export_data_failed)
|
||||
LauncherPreference(
|
||||
title = stringResource(id = R.string.settings_advanced_export_data),
|
||||
|
||||
@@ -17,9 +17,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.filled.SadPanda
|
||||
import com.hippo.ehviewer.ui.ABOUT_SETTINGS_SCREEN
|
||||
import com.hippo.ehviewer.ui.ADVANCED_SETTINGS_SCREEN
|
||||
import com.hippo.ehviewer.ui.DOWNLOAD_SETTINGS_SCREEN
|
||||
@@ -47,7 +48,7 @@ fun BaseScreen() {
|
||||
) {
|
||||
Column(modifier = Modifier.padding(it).nestedScroll(scrollBehavior.nestedScrollConnection)) {
|
||||
PreferenceHeader(
|
||||
icon = painterResource(id = R.drawable.v_sad_panda_primary_x24),
|
||||
icon = EhIcons.Default.SadPanda,
|
||||
title = R.string.settings_eh,
|
||||
childRouteName = EH_SETTINGS_SCREEN,
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ import com.hippo.ehviewer.ui.tools.TimePickerDialog
|
||||
import com.hippo.ehviewer.ui.tools.observed
|
||||
import com.hippo.ehviewer.ui.tools.rememberDialogState
|
||||
import com.hippo.ehviewer.ui.tools.rememberedAccessor
|
||||
import com.hippo.ehviewer.util.whisperClipboard
|
||||
import com.hippo.ehviewer.util.copyTextToClipboard
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
@@ -111,7 +111,7 @@ fun EhScreen() {
|
||||
val spanned = context.getString(R.string.settings_eh_identity_cookies_signed, str).parseAsHtml()
|
||||
setMessage(spanned)
|
||||
setNeutralButton(R.string.settings_eh_identity_cookies_copy) { _, _ ->
|
||||
context whisperClipboard str.replace("<br>", "\n")
|
||||
copyTextToClipboard(str.replace("<br>", "\n"), true)
|
||||
// Avoid double notify user since system have done that on Tiramisu above
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) launchSnackBar(copiedToClipboard)
|
||||
}
|
||||
@@ -213,7 +213,7 @@ fun EhScreen() {
|
||||
}
|
||||
}
|
||||
SimpleMenuPreferenceInt(
|
||||
title = stringResource(id = rikka.core.R.string.dark_theme),
|
||||
title = stringResource(id = R.string.dark_theme),
|
||||
entry = R.array.night_mode_entries,
|
||||
entryValueRes = R.array.night_mode_values,
|
||||
value = Settings::theme.observed,
|
||||
@@ -277,11 +277,22 @@ fun EhScreen() {
|
||||
summary = stringResource(id = R.string.settings_eh_show_gallery_pages_summary),
|
||||
value = Settings::showGalleryPages,
|
||||
)
|
||||
val showComments = Settings::showComments.observed
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_comments),
|
||||
summary = stringResource(id = R.string.settings_eh_show_gallery_comments_summary),
|
||||
value = Settings::showComments,
|
||||
value = showComments.rememberedAccessor,
|
||||
)
|
||||
AnimatedVisibility(visible = showComments.value) {
|
||||
IntSliderPreference(
|
||||
maxValue = 100,
|
||||
minValue = -101,
|
||||
showTicks = false,
|
||||
title = stringResource(id = R.string.settings_eh_show_gallery_comment_threshold),
|
||||
summary = stringResource(id = R.string.settings_eh_show_gallery_comment_threshold_summary),
|
||||
value = Settings::commentThreshold,
|
||||
)
|
||||
}
|
||||
if (EhTagDatabase.isTranslatable(context)) {
|
||||
SwitchPreference(
|
||||
title = stringResource(id = R.string.settings_eh_show_tag_translations),
|
||||
|
||||
@@ -49,8 +49,11 @@ import com.hippo.ehviewer.databinding.DialogAddFilterBinding
|
||||
import com.hippo.ehviewer.ui.LocalNavController
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.tools.Deferred
|
||||
import com.hippo.ehviewer.ui.tools.rememberDialogState
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.launch
|
||||
import moe.tarsin.coroutines.groupByToObserved
|
||||
|
||||
@Composable
|
||||
@@ -60,6 +63,8 @@ fun FilterScreen() {
|
||||
val scope = rememberCoroutineScope { Dispatchers.IO }
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||
val allFilterMap = remember { scope.async { EhFilter.filters.await().groupByToObserved { it.mode } } }
|
||||
val dialogState = rememberDialogState()
|
||||
dialogState.Intercept()
|
||||
class AddFilterDialogHelper(private val dialog: AlertDialog) : View.OnClickListener {
|
||||
private val mArray = dialog.context.resources.getStringArray(R.array.filter_entries)
|
||||
private val binding = DialogAddFilterBinding.bind(dialog.findViewById(R.id.base)!!)
|
||||
@@ -160,15 +165,22 @@ fun FilterScreen() {
|
||||
checked = filter.enable,
|
||||
onCheckedChange = { filter.trigger { filterCheckBoxRecomposeScope.invalidate() } },
|
||||
)
|
||||
Text(text = filter.text)
|
||||
Spacer(modifier = Modifier.weight(1F))
|
||||
Text(text = filter.text, modifier = Modifier.weight(1F))
|
||||
IconButton(
|
||||
onClick = {
|
||||
BaseDialogBuilder(context).setMessage(context.getString(R.string.delete_filter, filter.text)).setPositiveButton(R.string.delete) { _, which ->
|
||||
if (DialogInterface.BUTTON_POSITIVE == which) {
|
||||
filter.forget { filters.remove(filter) }
|
||||
scope.launch {
|
||||
dialogState.awaitPermissionOrCancel(
|
||||
confirmText = R.string.delete,
|
||||
dismissText = android.R.string.cancel,
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.delete_filter, filter.text))
|
||||
}
|
||||
}.setNegativeButton(android.R.string.cancel, null).show()
|
||||
filter.forget {
|
||||
withUIContext {
|
||||
filters.remove(filter)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Delete, contentDescription = null)
|
||||
|
||||
@@ -95,10 +95,10 @@ fun SwitchPreference(title: String, summary: String? = null, value: KMutableProp
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IntSliderPreference(maxValue: Int, minValue: Int = 0, step: Int = maxValue - minValue - 1, title: String, summary: String? = null, value: KMutableProperty0<Int>, enabled: Boolean = true) {
|
||||
fun IntSliderPreference(maxValue: Int, minValue: Int = 0, step: Int = maxValue - minValue - 1, showTicks: Boolean = true, title: String, summary: String? = null, value: KMutableProperty0<Int>, enabled: Boolean = true) {
|
||||
var v by remember { mutableIntStateOf(value.get()) }
|
||||
fun set(float: Float) = value.set(float.roundToInt().also { v = it })
|
||||
SliderPref(title = title, summary = summary, defaultValue = v.toFloat(), onValueChangeFinished = ::set, valueRange = minValue.toFloat()..maxValue.toFloat(), showValue = true, showInteger = true, steps = step, enabled = enabled)
|
||||
SliderPref(title = title, summary = summary, defaultValue = v.toFloat(), onValueChangeFinished = ::set, valueRange = minValue.toFloat()..maxValue.toFloat(), showValue = true, showInteger = true, steps = step, showTicks = showTicks, enabled = enabled)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -10,7 +10,7 @@ import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
|
||||
@Composable
|
||||
fun <T> Deferred(block: suspend () -> T, content: @Composable (T) -> Unit) {
|
||||
var completed by remember(block) { mutableStateOf<T?>(null) }
|
||||
var completed by remember { mutableStateOf<T?>(null) }
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
completed = withIOContext { block() }
|
||||
}
|
||||
|
||||
@@ -17,12 +17,10 @@
|
||||
*/
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipData
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
import android.os.Build
|
||||
import android.view.textclassifier.TextClassifier
|
||||
import androidx.core.os.persistableBundleOf
|
||||
@@ -31,15 +29,7 @@ import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.scene.BaseScene
|
||||
import splitties.systemservices.clipboardManager
|
||||
|
||||
infix fun Context.tellClipboardWithToast(text: String?) {
|
||||
addTextToClipboard(text, false, true)
|
||||
}
|
||||
|
||||
infix fun Context.whisperClipboard(text: String?) {
|
||||
addTextToClipboard(text, true, false)
|
||||
}
|
||||
|
||||
fun Context.addTextToClipboard(text: CharSequence?, isSensitive: Boolean, useToast: Boolean = false) {
|
||||
fun copyTextToClipboard(text: CharSequence?, isSensitive: Boolean) {
|
||||
clipboardManager.apply {
|
||||
setPrimaryClip(
|
||||
ClipData.newPlainText(null, text).apply {
|
||||
@@ -49,12 +39,14 @@ fun Context.addTextToClipboard(text: CharSequence?, isSensitive: Boolean, useToa
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.addTextToClipboard(text: CharSequence?, useToast: Boolean = false) {
|
||||
copyTextToClipboard(text, false)
|
||||
// Avoid double notify user since system have done that on Tiramisu above
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
val activity = findActivity()
|
||||
if (activity is MainActivity) {
|
||||
activity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT, useToast)
|
||||
}
|
||||
val activity = findActivity<MainActivity>()
|
||||
activity.showTip(R.string.copied_to_clipboard, BaseScene.LENGTH_SHORT, useToast)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,15 +58,3 @@ fun ClipboardManager.getUrlFromClipboard(context: Context): String? {
|
||||
val string = item?.coerceToText(context).toString()
|
||||
return string.ifEmpty { null }
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the closest Activity in a given Context.
|
||||
*/
|
||||
internal fun Context.findActivity(): Activity {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context
|
||||
context = context.baseContext
|
||||
}
|
||||
throw IllegalStateException("findActivity() should be called in the context of an Activity")
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.ContextWrapper
|
||||
|
||||
/**
|
||||
* Find the closest Activity in a given Context.
|
||||
*/
|
||||
inline fun <reified T : Activity> Context.findActivity(): T {
|
||||
var context = this
|
||||
while (context is ContextWrapper) {
|
||||
if (context is Activity) return context as T
|
||||
context = context.baseContext
|
||||
}
|
||||
throw IllegalStateException("findActivity() should be called in the context of an Activity")
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import rikka.core.util.ContextUtils.requireActivity
|
||||
|
||||
// Fuck off the silly Android launcher and callback :)
|
||||
|
||||
@@ -58,7 +57,7 @@ private suspend fun <I, O> Context.awaitActivityResult(contract: ActivityResultC
|
||||
lifecycle.addObserver(observer)
|
||||
return withUIContext {
|
||||
suspendCoroutine { cont -> // No cancellation support here since we cannot cancel a launched Intent
|
||||
val activity = requireActivity<ComponentActivity>(this@awaitActivityResult)
|
||||
val activity = findActivity<ComponentActivity>()
|
||||
launcher = activity.activityResultRegistry.register(key, contract) {
|
||||
launcher?.unregister()
|
||||
lifecycle.removeObserver(observer)
|
||||
|
||||
@@ -5,6 +5,8 @@ package com.hippo.ehviewer.util
|
||||
import kotlin.reflect.KMutableProperty0
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
fun <T> unsafeLazy(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer)
|
||||
|
||||
interface Lazy<T> {
|
||||
var value: T
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ package com.hippo.ehviewer.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.webkit.WebView
|
||||
import com.hippo.ehviewer.client.CHROME_USER_AGENT
|
||||
import com.hippo.ehviewer.Settings
|
||||
|
||||
@SuppressLint("SetJavaScriptEnabled")
|
||||
fun WebView.setDefaultSettings() = settings.run {
|
||||
builtInZoomControls = true
|
||||
displayZoomControls = false
|
||||
javaScriptEnabled = true
|
||||
userAgentString = CHROME_USER_AGENT
|
||||
userAgentString = Settings.userAgent
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.hippo.ehviewer.util
|
||||
|
||||
import android.view.View
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.core.graphics.Insets
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import kotlin.contracts.InvocationKind
|
||||
import kotlin.contracts.contract
|
||||
|
||||
inline fun buildWindowInsets(builderAction: WindowInsetsCompat.Builder.() -> Unit): WindowInsetsCompat {
|
||||
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
|
||||
val builder = WindowInsetsCompat.Builder()
|
||||
builder.builderAction()
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WindowInsetsCompat.Builder.set(type: Int, insets: WindowInsets): WindowInsetsCompat.Builder {
|
||||
val density = LocalDensity.current
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
return setInsets(
|
||||
type,
|
||||
Insets.of(
|
||||
insets.getLeft(density, layoutDirection),
|
||||
insets.getTop(density),
|
||||
insets.getRight(density, layoutDirection),
|
||||
insets.getBottom(density),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun View.applyNavigationBarsPadding() {
|
||||
applyInsetter {
|
||||
type(navigationBars = true) {
|
||||
padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
@@ -30,6 +31,7 @@ fun SliderPref(
|
||||
valueRange: ClosedFloatingPointRange<Float> = 0f..1f,
|
||||
showValue: Boolean = false,
|
||||
showInteger: Boolean = false,
|
||||
showTicks: Boolean = true,
|
||||
steps: Int = 0,
|
||||
textColor: Color = MaterialTheme.colorScheme.onBackground,
|
||||
enabled: Boolean = true,
|
||||
@@ -61,6 +63,14 @@ fun SliderPref(
|
||||
steps = steps,
|
||||
onValueChangeFinished = { onValueChangeFinished?.invoke(value) },
|
||||
enabled = enabled,
|
||||
colors = if (showTicks) {
|
||||
SliderDefaults.colors()
|
||||
} else {
|
||||
SliderDefaults.colors(
|
||||
activeTickColor = Color.Transparent,
|
||||
inactiveTickColor = Color.Transparent,
|
||||
)
|
||||
},
|
||||
)
|
||||
if (showValue) {
|
||||
Text(
|
||||
|
||||
+5
-9
@@ -8,7 +8,6 @@ import android.webkit.WebViewClient
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.hippo.ehviewer.client.EhCookieStore
|
||||
import com.hippo.ehviewer.client.exception.CloudflareBypassException
|
||||
import java.io.IOException
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
@@ -27,14 +26,11 @@ class CloudflareInterceptor(context: Context) : WebViewInterceptor(context) {
|
||||
request: Request,
|
||||
response: Response,
|
||||
): Response {
|
||||
// Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
|
||||
// we don't crash the entire app
|
||||
return runCatching {
|
||||
response.close()
|
||||
EhCookieStore.deleteCookie(request.url, COOKIE_NAME)
|
||||
resolveWithWebView(request)
|
||||
chain.proceed(request)
|
||||
}.getOrElse { throw IOException(it) }
|
||||
response.close()
|
||||
EhCookieStore.deleteCookie(request.url, COOKIE_NAME)
|
||||
resolveWithWebView(request)
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
private fun resolveWithWebView(originalRequest: Request) {
|
||||
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package eu.kanade.tachiyomi.network.interceptor
|
||||
|
||||
import java.io.IOException
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
/**
|
||||
* Catches any uncaught exceptions from later in the chain and rethrows as a non-fatal
|
||||
* IOException to avoid catastrophic failure.
|
||||
*
|
||||
* This should be the first interceptor in the client.
|
||||
*
|
||||
* See [Interceptor](https://square.github.io/okhttp/5.x/okhttp/okhttp3/-interceptor/index.html)
|
||||
*/
|
||||
class UncaughtExceptionInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
return try {
|
||||
chain.proceed(chain.request())
|
||||
} catch (e: Exception) {
|
||||
if (e is IOException) {
|
||||
throw e
|
||||
} else {
|
||||
throw IOException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.result.contract.ActivityResultContracts.CreateDocument
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.ChecksSdkIntAtLeast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
@@ -59,6 +60,7 @@ import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.shape.MaterialShapeDrawable
|
||||
import com.hippo.ehviewer.BuildConfig
|
||||
@@ -78,9 +80,12 @@ import com.hippo.ehviewer.util.ExceptionUtils
|
||||
import com.hippo.ehviewer.util.FileUtils
|
||||
import com.hippo.ehviewer.util.getParcelableCompat
|
||||
import com.hippo.ehviewer.util.getParcelableExtraCompat
|
||||
import com.hippo.ehviewer.util.getValue
|
||||
import com.hippo.ehviewer.util.isAtLeastO
|
||||
import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.lazyMut
|
||||
import com.hippo.ehviewer.util.sendTo
|
||||
import com.hippo.ehviewer.util.setValue
|
||||
import com.hippo.unifile.UniFile
|
||||
import dev.chrisbanes.insetter.applyInsetter
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
@@ -114,8 +119,18 @@ import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.sample
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import splitties.init.appCtx
|
||||
import splitties.systemservices.clipboardManager
|
||||
|
||||
class GalleryModel : ViewModel() {
|
||||
var galleryProvider: PageLoader2? = null
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
galleryProvider?.stop()
|
||||
galleryProvider = null
|
||||
}
|
||||
}
|
||||
|
||||
class ReaderActivity : EhActivity() {
|
||||
lateinit var binding: ReaderActivityBinding
|
||||
private var mAction: String? = null
|
||||
@@ -124,6 +139,7 @@ class ReaderActivity : EhActivity() {
|
||||
private var mGalleryInfo: BaseGalleryInfo? = null
|
||||
private var mPage: Int = 0
|
||||
private var mCacheFileName: String? = null
|
||||
private val vm: GalleryModel by viewModels()
|
||||
|
||||
/**
|
||||
* Whether the menu is currently visible.
|
||||
@@ -159,7 +175,7 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
var mGalleryProvider: PageLoader2? = null
|
||||
var mGalleryProvider by lazyMut { vm::galleryProvider }
|
||||
private var mCurrentIndex: Int = 0
|
||||
private var mSavingPage = -1
|
||||
private lateinit var builder: EditTextDialogBuilder
|
||||
@@ -190,13 +206,6 @@ class ReaderActivity : EhActivity() {
|
||||
}
|
||||
|
||||
private fun buildProvider(replace: Boolean = false) {
|
||||
// TODO: Better solution for this dirty workaround
|
||||
if (setOrientation(ReaderPreferences.defaultOrientationType().get())) {
|
||||
// Orientation changed, we don't know if the activity will recreate, just recreate anyway
|
||||
recreate()
|
||||
return
|
||||
}
|
||||
|
||||
if (mGalleryProvider != null) {
|
||||
if (replace) mGalleryProvider!!.stop() else return
|
||||
}
|
||||
@@ -218,7 +227,7 @@ class ReaderActivity : EhActivity() {
|
||||
|
||||
val continuation: AtomicReference<Continuation<String>?> = AtomicReference(null)
|
||||
mGalleryProvider = ArchivePageLoader(
|
||||
this,
|
||||
appCtx,
|
||||
mUri!!,
|
||||
flow {
|
||||
if (!dialogShown) {
|
||||
@@ -271,7 +280,6 @@ class ReaderActivity : EhActivity() {
|
||||
|
||||
private fun onInit() {
|
||||
handleIntent(intent)
|
||||
buildProvider()
|
||||
}
|
||||
|
||||
private fun onRestore(savedInstanceState: Bundle) {
|
||||
@@ -281,7 +289,6 @@ class ReaderActivity : EhActivity() {
|
||||
mGalleryInfo = savedInstanceState.getParcelableCompat(KEY_GALLERY_INFO)
|
||||
mPage = savedInstanceState.getInt(KEY_PAGE, -1)
|
||||
mCurrentIndex = savedInstanceState.getInt(KEY_CURRENT_INDEX)
|
||||
buildProvider()
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
@@ -328,6 +335,7 @@ class ReaderActivity : EhActivity() {
|
||||
} else {
|
||||
onRestore(savedInstanceState)
|
||||
}
|
||||
buildProvider()
|
||||
binding = ReaderActivityBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
builder = EditTextDialogBuilder(this, null, getString(R.string.archive_passwd))
|
||||
@@ -338,6 +346,7 @@ class ReaderActivity : EhActivity() {
|
||||
|
||||
mGalleryProvider.let {
|
||||
if (it == null) {
|
||||
finish()
|
||||
return
|
||||
}
|
||||
lifecycleScope.launchIO {
|
||||
@@ -368,7 +377,6 @@ class ReaderActivity : EhActivity() {
|
||||
updateViewerInset(ReaderPreferences.fullscreen().get())
|
||||
binding.viewerContainer.removeAllViews()
|
||||
setOrientation(ReaderPreferences.defaultOrientationType().get())
|
||||
updateOrientationShortcut(ReaderPreferences.defaultOrientationType().get())
|
||||
binding.viewerContainer.addView(viewer?.getView())
|
||||
viewer?.setGalleryProvider(mGalleryProvider!!)
|
||||
moveToPageIndex(mCurrentIndex)
|
||||
@@ -379,10 +387,6 @@ class ReaderActivity : EhActivity() {
|
||||
config = null
|
||||
viewer?.destroy()
|
||||
viewer = null
|
||||
if (mGalleryProvider != null) {
|
||||
mGalleryProvider!!.stop()
|
||||
mGalleryProvider = null
|
||||
}
|
||||
readerSettingSheetDialog?.dismiss()
|
||||
readerSettingSheetDialog = null
|
||||
}
|
||||
@@ -856,15 +860,13 @@ class ReaderActivity : EhActivity() {
|
||||
|
||||
/**
|
||||
* Forces the user preferred [orientation] on the activity.
|
||||
* Returns true if a restart is required.
|
||||
*/
|
||||
private fun setOrientation(orientation: Int): Boolean {
|
||||
private fun setOrientation(orientation: Int) {
|
||||
val newOrientation = OrientationType.fromPreference(orientation)
|
||||
val restart = newOrientation.flag != requestedOrientation
|
||||
if (restart) {
|
||||
if (newOrientation.flag != requestedOrientation) {
|
||||
requestedOrientation = newOrientation.flag
|
||||
}
|
||||
return restart
|
||||
updateOrientationShortcut(ReaderPreferences.defaultOrientationType().get())
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ fun CombinedCircularProgressIndicator(progress: Float) {
|
||||
label = "rotation",
|
||||
)
|
||||
CircularProgressIndicator(
|
||||
progress = { animatedProgress },
|
||||
progress = animatedProgress,
|
||||
modifier = Modifier.rotate(rotation),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
package rikka.core.res
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.ColorInt
|
||||
|
||||
@ColorInt
|
||||
fun Resources.Theme.resolveColor(@AttrRes attrId: Int): Int {
|
||||
val a = obtainStyledAttributes(intArrayOf(attrId))
|
||||
val res = a.getColor(0, 0)
|
||||
a.recycle()
|
||||
return res
|
||||
}
|
||||
|
||||
fun Resources.Theme.resolveDrawable(@AttrRes attrId: Int): Drawable? {
|
||||
val a = obtainStyledAttributes(intArrayOf(attrId))
|
||||
val res = a.getDrawable(0)
|
||||
a.recycle()
|
||||
return res
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="120dp"
|
||||
android:height="120dp"
|
||||
android:tint="?colorTertiary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_big_history" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2022 Tarsin Norbin
|
||||
~
|
||||
~ This file is part of EhViewer
|
||||
~
|
||||
~ EhViewer is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published
|
||||
~ by the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ EhViewer is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M2,17h2v0.5L3,17.5v1h1v0.5L2,19v1h3v-4L2,16v1zM3,8h1L4,4L2,4v1h1v3zM2,11h1.8L2,13.1v0.9h3v-1L3.2,13L5,10.9L5,10L2,10v1zM7,5v2h14L21,5L7,5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2z" />
|
||||
</vector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2019 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_eh_subscription" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2022 Tarsin Norbin
|
||||
~
|
||||
~ This file is part of EhViewer
|
||||
~
|
||||
~ EhViewer is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published
|
||||
~ by the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ EhViewer is distributed in the hope that it will be useful, but WITHOUT
|
||||
~ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
~ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License along with EhViewer.
|
||||
~ If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z" />
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_fire" />
|
||||
|
||||
</vector>
|
||||
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2019 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_heart" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_history" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_homepage" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?colorPrimary"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_sad_panda" />
|
||||
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="@string/pd_settings" />
|
||||
|
||||
</vector>
|
||||
@@ -14,40 +14,10 @@
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/draw_view"
|
||||
android:id="@+id/fragment_container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
app:edgeToEdge="true">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/snackbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:fitsSystemWindowsInsets="start|end">
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.AboveSnackerLayout
|
||||
android:id="@+id/absnacker"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/fragment_container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true" />
|
||||
</com.hippo.ehviewer.ui.legacy.AboveSnackerLayout>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:headerLayout="@layout/nav_header_main"
|
||||
app:menu="@menu/nav_drawer_main" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
app:defaultNavHost="true" />
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<FrameLayout 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="160dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@drawable/sadpanda_low_poly" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -21,25 +21,26 @@
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
android:clipChildren="false"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/gallery_padding_top_search_bar"
|
||||
android:paddingHorizontal="@dimen/gallery_list_margin_h"
|
||||
android:paddingVertical="@dimen/gallery_list_margin_v"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:paddingVertical="@dimen/gallery_list_margin_v" />
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.FastScroller
|
||||
android:id="@+id/fast_scroller"
|
||||
android:layout_width="30dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end"
|
||||
android:layout_marginTop="@dimen/gallery_padding_top_search_bar"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingBottom="8dp"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
@@ -58,8 +59,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:fitsSystemWindowsInsets="bottom">
|
||||
android:paddingBottom="@dimen/corner_fab_margin">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:fitsSystemWindowsInsets="bottom">
|
||||
android:paddingBottom="@dimen/corner_fab_margin">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
android:clipToPadding="false"
|
||||
android:paddingLeft="@dimen/keyline_margin"
|
||||
android:paddingRight="@dimen/keyline_margin"
|
||||
android:paddingBottom="@dimen/fab_rv_padding_size"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:paddingBottom="@dimen/fab_rv_padding_size" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout>
|
||||
|
||||
@@ -43,8 +42,7 @@
|
||||
android:layout_gravity="center"
|
||||
android:drawablePadding="8dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/no_one_comments_gallery"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:text="@string/no_one_comments_gallery" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/edit_panel"
|
||||
@@ -54,8 +52,7 @@
|
||||
android:background="?attr/colorPrimaryContainer"
|
||||
android:elevation="4dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="invisible"
|
||||
app:fitsSystemWindowsInsets="bottom">
|
||||
android:visibility="invisible">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_text"
|
||||
@@ -90,8 +87,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:fitsSystemWindowsInsets="bottom">
|
||||
android:paddingBottom="@dimen/corner_fab_margin">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
|
||||
@@ -27,16 +27,14 @@
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="@dimen/gallery_search_bar_margin_v"
|
||||
android:layout_marginEnd="@dimen/gallery_search_bar_margin_v"
|
||||
android:paddingTop="80dp"
|
||||
app:layout_fitsSystemWindowsInsets="top" />
|
||||
android:paddingTop="@dimen/gallery_padding_top_search_bar" />
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
android:id="@+id/fab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:layout_fitsSystemWindowsInsets="bottom">
|
||||
android:paddingBottom="@dimen/corner_fab_margin">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
style="@style/Widget.Material3.FloatingActionButton.Small.Primary"
|
||||
@@ -62,4 +60,21 @@
|
||||
|
||||
</com.hippo.ehviewer.ui.legacy.FabLayout>
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/search_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleX="0"
|
||||
android:scaleY="0"
|
||||
android:visibility="invisible"
|
||||
app:srcCompat="@drawable/v_magnify_x24" />
|
||||
|
||||
</com.hippo.ehviewer.ui.legacy.FabLayout>
|
||||
|
||||
</merge>
|
||||
@@ -47,22 +47,4 @@
|
||||
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingEnd="@dimen/corner_fab_margin"
|
||||
android:paddingBottom="@dimen/corner_fab_margin"
|
||||
app:fitsSystemWindowsInsets="top|bottom">
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/search_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleX="0"
|
||||
android:scaleY="0"
|
||||
android:visibility="invisible"
|
||||
app:srcCompat="@drawable/v_magnify_x24" />
|
||||
|
||||
</com.hippo.ehviewer.ui.legacy.FabLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -20,8 +20,6 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipChildren="false"
|
||||
android:paddingTop="16dp"
|
||||
app:fitsSystemWindowsInsets="top"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
@@ -54,8 +52,7 @@
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:paddingHorizontal="@dimen/gallery_list_margin_h"
|
||||
android:paddingVertical="@dimen/gallery_list_margin_v"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:paddingVertical="@dimen/gallery_list_margin_v" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.ThemedSwipeRefreshLayout>
|
||||
|
||||
@@ -67,7 +64,6 @@
|
||||
android:layout_marginTop="@dimen/gallery_padding_top_search_bar"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingVertical="8dp"
|
||||
app:fitsSystemWindowsInsets="bottom" />
|
||||
android:paddingVertical="8dp" />
|
||||
|
||||
</FrameLayout>
|
||||
@@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright 2016 Hippo Seven
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<group android:checkableBehavior="single">
|
||||
<item
|
||||
android:id="@+id/nav_homepage"
|
||||
android:icon="@drawable/v_homepage_black_x24"
|
||||
android:title="@string/homepage" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_subscription"
|
||||
android:icon="@drawable/v_eh_subscription_black_x24"
|
||||
android:title="@string/subscription" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_whats_hot"
|
||||
android:icon="@drawable/v_fire_black_x24"
|
||||
android:title="@string/whats_hot" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_toplist"
|
||||
android:icon="@drawable/ic_baseline_format_list_numbered_24"
|
||||
android:title="@string/toplist" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_favourite"
|
||||
android:icon="@drawable/v_heart_x24"
|
||||
android:title="@string/favourite" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_history"
|
||||
android:icon="@drawable/v_history_black_x24"
|
||||
android:title="@string/history" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_downloads"
|
||||
android:icon="@drawable/v_download_x24"
|
||||
android:title="@string/downloads" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_settings"
|
||||
android:icon="@drawable/v_settings_black_x24"
|
||||
android:title="@string/settings" />
|
||||
|
||||
<item
|
||||
android:id="@+id/nav_stub"
|
||||
android:title="@string/stub"
|
||||
android:visible="false" />
|
||||
|
||||
</group>
|
||||
|
||||
</menu>
|
||||
@@ -1,8 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/nav_graph"
|
||||
app:startDestination="@id/galleryListScene">
|
||||
android:id="@+id/nav_graph">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/galleryListScene"
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<string name="gallery_list_search_bar_hint_e_hentai">E-Hentai durchsuchen</string>
|
||||
<string name="gallery_list_empty_hit">Die Welt ist so groß und der Panda sitzt ganz allein</string>
|
||||
<string name="search">Suchen</string>
|
||||
<string name="keyword_search">"Suche nach dem Schlüsselwort "</string>
|
||||
<string name="keyword_search">Suche nach dem Schlüsselwort</string>
|
||||
<string name="image_search">Suche nach einem Bild</string>
|
||||
<string name="search_normal">Normale Suche</string>
|
||||
<string name="search_advance">Erweiterte Optionen</string>
|
||||
@@ -204,7 +204,7 @@
|
||||
<string name="download_remove_dialog_check_text">Bilddateien löschen</string>
|
||||
<string name="stat_download_action_stop_all">Alle stopppen</string>
|
||||
<string name="default_download_label_name">Standard</string>
|
||||
<string name="download_move_dialog_title">"Verschieben "</string>
|
||||
<string name="download_move_dialog_title">Verschieben</string>
|
||||
<string name="download_labels">Download-Etikette</string>
|
||||
<string name="download_start_all">Alle starten</string>
|
||||
<string name="download_stop_all">Alle stoppen</string>
|
||||
@@ -303,7 +303,6 @@
|
||||
<string name="default_favorites_collection">Standard-Favoriten</string>
|
||||
<string name="let_me_select">Manuell auswählen</string>
|
||||
<string name="collections">Kollektionen</string>
|
||||
<string name="need_sign_in">Du musst dich zuerst anmelden</string>
|
||||
<string name="error_something_wrong_happened">Ein Fehler ist aufgetreten</string>
|
||||
<string name="from_the_future">Von der Zukunft</string>
|
||||
<string name="just_now">Gerade</string>
|
||||
@@ -312,7 +311,6 @@
|
||||
<string name="no_archives">Keine Archivdatei</string>
|
||||
<string name="download_archive_started">Archivdatei wird heruntergeladen</string>
|
||||
<string name="download_archive_failure">Fehler beim Herunterladen der Archivdatei</string>
|
||||
<string name="archive">Archivdateien</string>
|
||||
<string name="download_archive_failure_no_hath">H@H-Klient ist für das Herunterladen der Archivdateien erforderlich</string>
|
||||
<string name="settings_privacy">Datenschutzoptionen</string>
|
||||
<string name="settings_privacy_secure">Screenshot verhindern</string>
|
||||
@@ -349,5 +347,6 @@
|
||||
<string name="settings_eh_identity_cookies_copy">Kopieren</string>
|
||||
<string name="settings_eh_show_gallery_pages">Galerieseitenzahl anzeigen</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">Die Galerieseitenzahle in der Galerieliste anzeigen</string>
|
||||
<string name="dark_theme_follow_system">System folgen</string>
|
||||
|
||||
</resources>
|
||||
@@ -363,7 +363,6 @@
|
||||
<string name="default_favorites_collection">Colección de favoritos por defecto</string>
|
||||
<string name="let_me_select">Déjame elegir</string>
|
||||
<string name="collections">Colección</string>
|
||||
<string name="need_sign_in">Se necesita iniciar sesión</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
|
||||
@@ -411,7 +410,6 @@
|
||||
<string name="cancel_vote_down">Cancelar voto negativo</string>
|
||||
<string name="clear_all">Limpiar todo</string>
|
||||
<string name="comment_failed">Comentario fallido</string>
|
||||
<string name="archive">Archivo</string>
|
||||
<string name="delete">Eliminar</string>
|
||||
<string name="download_state_failed">Descarga fallida</string>
|
||||
<string name="download_state_none">Descarga en pausa</string>
|
||||
@@ -435,5 +433,6 @@
|
||||
<string name="settings_privacy_secure_summary">Si habilitas esta opción no podrás hacer captura de pantalla y el sistema no mostrará una previsualización en el multitareas.\n\nDeberás reiniciar la aplicación para activar esta función.</string>
|
||||
<string name="u_config">Ajustes EHentai</string>
|
||||
<string name="apply_tip">Toca la marca de verificación para guardar los cambios</string>
|
||||
<string name="dark_theme_follow_system">Sigue el sistema</string>
|
||||
|
||||
</resources>
|
||||
@@ -402,7 +402,6 @@
|
||||
<string name="default_favorites_collection">Collection favoris par défaut</string>
|
||||
<string name="let_me_select">Sélectionner manuellement</string>
|
||||
<string name="collections">Collections</string>
|
||||
<string name="need_sign_in">Vous devez vous connecter</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">Une erreur s\'est produite</string>
|
||||
@@ -445,7 +444,6 @@
|
||||
<string name="no_archives">Aucun fiche archives</string>
|
||||
<string name="download_archive_started">Téléchargement du fiche archives a commencé</string>
|
||||
<string name="download_archive_failure">Téléchargement du fiche archives a échoué</string>
|
||||
<string name="archive">Fiches archives</string>
|
||||
<string name="download_archive_failure_no_hath">Client H@H est nécessaire pour le téléchargement de fiches archives</string>
|
||||
<string name="settings_privacy">Confidentialité</string>
|
||||
<string name="settings_privacy_secure">Empêcher les captures d\'ecran</string>
|
||||
@@ -457,5 +455,9 @@
|
||||
<string name="settings_eh_identity_cookies_copy">Copier</string>
|
||||
<string name="settings_eh_show_gallery_pages">Montrer les nombres des pages des galeries</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">Montrer le nombre des pages des galerie dans la liste des galeries</string>
|
||||
<string name="dark_theme">Thème sombre</string>
|
||||
<string name="dark_theme_follow_system">Identique au système</string>
|
||||
<string name="dark_theme_off">Toujours actif</string>
|
||||
<string name="dark_theme_on">Toujours inactif</string>
|
||||
|
||||
</resources>
|
||||
@@ -399,7 +399,6 @@
|
||||
<string name="default_favorites_collection">既定のお気に入りリスト</string>
|
||||
<string name="let_me_select">手動で選択</string>
|
||||
<string name="collections">お気に入りコレクション</string>
|
||||
<string name="need_sign_in">ログインが必要</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">エラーが発生しました</string>
|
||||
@@ -436,7 +435,6 @@
|
||||
<string name="no_archives">圧縮パッケージなし</string>
|
||||
<string name="download_archive_started">圧縮パッケージのダウンロードを開始しました</string>
|
||||
<string name="download_archive_failure">圧縮パッケージをダウンロードできませんでした</string>
|
||||
<string name="archive">圧縮パッケージ</string>
|
||||
<string name="download_archive_failure_no_hath">圧縮パッケージのダウンロードにはH@Hクライアントが必要です</string>
|
||||
<string name="settings_privacy">プライバシー</string>
|
||||
<string name="settings_privacy_secure">スクリーンキャプチャを防ぐ</string>
|
||||
@@ -455,4 +453,5 @@
|
||||
<string name="settings_eh_identity_cookies_copy">コピー</string>
|
||||
<string name="settings_eh_show_gallery_pages">ギャラリーページ数を表示</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">リストにギャラリーのページの数量を表示します</string>
|
||||
<string name="dark_theme">暗いテーマ</string>
|
||||
</resources>
|
||||
@@ -373,7 +373,6 @@
|
||||
<string name="default_favorites_collection">기본 즐겨찾기 컬렉션</string>
|
||||
<string name="let_me_select">직접 선택</string>
|
||||
<string name="collections">컬렉션</string>
|
||||
<string name="need_sign_in">로그인 필요</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">뭔가 잘못되었습니다.</string>
|
||||
@@ -409,7 +408,6 @@
|
||||
<string name="no_archives">아카이브 없음</string>
|
||||
<string name="download_archive_started">아카이브 다운로드가 시작되었습니다.</string>
|
||||
<string name="download_archive_failure">아카이브 다운로드에 실패했습니다.</string>
|
||||
<string name="archive">아카이브</string>
|
||||
<string name="download_archive_failure_no_hath">아카이브를 다운로드하려면 H@H 클라이언트가 필요합니다.</string>
|
||||
<string name="settings_privacy">개인 정보</string>
|
||||
<string name="settings_privacy_secure">화면 캡처 허용 안 함</string>
|
||||
@@ -417,4 +415,8 @@
|
||||
|
||||
<string name="download_service">다운로드 서비스</string>
|
||||
<string name="favorite_name">즐겨찾기</string>
|
||||
<string name="dark_theme">어두운 테마</string>
|
||||
<string name="dark_theme_follow_system">시스템 설정</string>
|
||||
<string name="dark_theme_off">항상 꺼짐</string>
|
||||
<string name="dark_theme_on">항상 켜짐</string>
|
||||
</resources>
|
||||
@@ -30,7 +30,6 @@
|
||||
<string name="no_torrents">Ingen torrenter</string>
|
||||
<string name="search_sfl">Språk</string>
|
||||
<string name="search_osc">Kun søk i omslag</string>
|
||||
<string name="error_empty_html">Tom side</string>
|
||||
<string name="tourist_mode">Fortsett uten å logge inn</string>
|
||||
<string name="select_scene">Hvilken galleriside vil du besøke\?</string>
|
||||
<string name="favored_times">♥ %d</string>
|
||||
@@ -197,7 +196,6 @@
|
||||
<string name="settings_eh_thumb_resolution_auto">Automatisk</string>
|
||||
<string name="app_language_system">Systemspråk (forvalg)</string>
|
||||
<string name="error_something_wrong_happened">Noe gikk galt</string>
|
||||
<string name="need_sign_in">Innlogging kreves</string>
|
||||
<string name="just_now">Akkurat nå</string>
|
||||
<string name="move_favorites_dialog_title">Flytt favoritter</string>
|
||||
<string name="let_me_select">La meg velge</string>
|
||||
|
||||
@@ -21,15 +21,8 @@
|
||||
|
||||
<dimen name="strip_item_padding">12dp</dimen>
|
||||
|
||||
<!-- 24dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_margin_h">20dp</dimen>
|
||||
<!-- 24dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_grid_margin_h">20dp</dimen>
|
||||
|
||||
<!-- 24dp - 4dp, 4dp for card padding -->
|
||||
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<!-- 24dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="search_layout_margin_h">20dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -16,14 +16,8 @@
|
||||
|
||||
<resources>
|
||||
|
||||
<!-- 80dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_margin_h">76dp</dimen>
|
||||
<!-- 80dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_grid_margin_h">76dp</dimen>
|
||||
|
||||
<!-- 80dp - 4dp, 4dp for card padding -->
|
||||
|
||||
<!-- 80dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="search_layout_margin_h">76dp</dimen>
|
||||
|
||||
</resources>
|
||||
@@ -388,7 +388,6 @@
|
||||
<string name="default_favorites_collection">ค่าเริ่มต้นของที่เก็บรายการโปรด</string>
|
||||
<string name="let_me_select">ขอให้ฉันเลือกนะ</string>
|
||||
<string name="collections">คอลเลคชั่น</string>
|
||||
<string name="need_sign_in">จำเป็นต้องลงชื่อเข้าใช้ก่อน</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">มีสิ่งผิดปกติบางอย่างเกิดขึ้น</string>
|
||||
@@ -424,7 +423,6 @@
|
||||
<string name="no_archives">ยังไม่มีไฟล์บีบอัด</string>
|
||||
<string name="download_archive_started">เริ่มการดาวน์โหลดบีบอัด</string>
|
||||
<string name="download_archive_failure">การดาวน์โหลดไฟล์บีบอัดล้มเหลว</string>
|
||||
<string name="archive">การบีบอัด</string>
|
||||
<string name="download_archive_failure_no_hath">ต้องการ H@H client เพื่อโหลดไฟล์บีบอัด</string>
|
||||
<string name="settings_privacy">ความเป็นส่วนตัว</string>
|
||||
<string name="settings_privacy_secure">ป้องกันการบันทึกภาพหน้าจอ</string>
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
<string name="error_cant_create_temp_file">Geçici dosya oluşturulamadı</string>
|
||||
<string name="error_cant_save_image">Görsel kaydedilemiyor</string>
|
||||
<string name="error_invalid_number">Geçersiz numara</string>
|
||||
<string name="error_empty_html">Boş sayfa</string>
|
||||
<string name="kokomade_tip">Ayarlar->Eh->Gallery Site->e-hentai</string>
|
||||
<string name="error_username_cannot_empty">Kullanıcı adı boş bir metin olamaz</string>
|
||||
<string name="error_password_cannot_empty">Şifre boş bir metin olamaz</string>
|
||||
<string name="tourist_mode">Giriş yapmaksızın devam et</string>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string-array name="color_filter_modes">
|
||||
<item>@string/label_default</item>
|
||||
<item>@string/filter_mode_multiply</item>
|
||||
<item>@string/filter_mode_screen</item>
|
||||
|
||||
<!-- Attributes specific for SDK 28 and up -->
|
||||
<item>@string/filter_mode_overlay</item>
|
||||
<item>@string/filter_mode_lighten</item>
|
||||
<item>@string/filter_mode_darken</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
||||
@@ -54,8 +54,6 @@
|
||||
<string name="error_cant_create_temp_file">无法创建临时文件</string>
|
||||
<string name="error_cant_save_image">无法保存图片</string>
|
||||
<string name="error_invalid_number">非法数字</string>
|
||||
<string name="error_empty_html">页面内容为空</string>
|
||||
<string name="kokomade_tip">设置->Eh->画廊站点->e-hentai</string>
|
||||
<string name="app_waring">本应用中内容来自互联网,部分内容可能对您的生理及心理造成难以恢复的伤害。本应用作者不会对由本应用造成的任何后果负责。未成年人应在监护人指导下使用本应用。请遵守所在地的法律法规。</string>
|
||||
<string name="app_waring_2">继续使用即代表您同意上述条款。</string>
|
||||
<string name="error_username_cannot_empty">用户名不可为空</string>
|
||||
@@ -330,13 +328,15 @@
|
||||
<string name="settings_eh_thumb_resolution_summary">%s,指定分辨率可能使获取缩略图失败</string>
|
||||
<string name="settings_eh_thumb_resolution_auto">自动</string>
|
||||
<string name="settings_eh_force_eh_thumb">强制使用 e-hentai 缩略图/API服务器</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">若 exhentai 加载失败可尝试此项</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">若 exhentai 加载失败可尝试此项\n注意此项将使用 e-hentai 设置加载图像</string>
|
||||
<string name="settings_eh_show_jpn_title">显示日文标题</string>
|
||||
<string name="settings_eh_show_jpn_title_summary">需同时在 EHentai 网站设置中启用 Japanese Title</string>
|
||||
<string name="settings_eh_show_gallery_pages">显示画廊页数</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">在画廊列表中显示页数</string>
|
||||
<string name="settings_eh_show_gallery_comments">显示画廊评论</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">在画廊详情页中显示评论</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold">评论分数阈值</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold_summary">隐藏低于或等于此分数的评论(-101 禁用)</string>
|
||||
<string name="settings_eh_show_tag_translations">显示标签翻译</string>
|
||||
<string name="settings_eh_show_tag_translations_summary">显示翻译后的标签而非原始文字(需花费时间来下载数据文件)</string>
|
||||
<string name="settings_eh_tag_translations_source">补充翻译(由 EhTagTranslation 提供)</string>
|
||||
@@ -423,7 +423,6 @@
|
||||
<string name="let_me_select">让我选择</string>
|
||||
<string name="favorite_note">收藏备注</string>
|
||||
<string name="collections">收藏夹</string>
|
||||
<string name="need_sign_in">需要登录</string>
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">被玩坏了</string>
|
||||
<!-- Guide -->
|
||||
@@ -456,7 +455,6 @@
|
||||
<string name="no_archives">没有压缩包</string>
|
||||
<string name="download_archive_started">开始下载压缩包</string>
|
||||
<string name="download_archive_failure">无法下载压缩包</string>
|
||||
<string name="archive">压缩包</string>
|
||||
<string name="archive_free">免费</string>
|
||||
<string name="archive_original">原始档案</string>
|
||||
<string name="archive_resample">重采样档案</string>
|
||||
@@ -585,6 +583,9 @@
|
||||
<string name="pref_color_filter_mode">滤镜调和模式</string>
|
||||
<string name="filter_mode_multiply">色彩增值</string>
|
||||
<string name="filter_mode_screen">滤色</string>
|
||||
<string name="filter_mode_overlay">覆盖</string>
|
||||
<string name="filter_mode_lighten">减淡/变亮</string>
|
||||
<string name="filter_mode_darken">加深/变暗</string>
|
||||
<string name="pref_cutout_short">在刘海屏区域显示内容</string>
|
||||
<string name="action_menu">菜单</string>
|
||||
<string name="webtoon_side_padding_25">25%</string>
|
||||
@@ -625,5 +626,9 @@
|
||||
<string name="disabled_nav">关闭</string>
|
||||
<string name="wide_color_gamut">使用Display P3色彩空间</string>
|
||||
<string name="settings_eh_request_news_timepicker">设置请求新闻页面的时间</string>
|
||||
<string name="dark_theme">深色主题</string>
|
||||
<string name="dark_theme_follow_system">跟随系统</string>
|
||||
<string name="dark_theme_off">总是关闭</string>
|
||||
<string name="dark_theme_on">总是开启</string>
|
||||
<!-- Tachiyomi Reader End-->
|
||||
</resources>
|
||||
@@ -63,9 +63,6 @@
|
||||
<string name="error_cant_create_temp_file">無法創建臨時文件</string>
|
||||
<string name="error_cant_save_image">無法保存圖片</string>
|
||||
<string name="error_invalid_number">非法數字</string>
|
||||
<string name="error_empty_html">頁面內容為空</string>
|
||||
|
||||
<string name="kokomade_tip">設置->Eh->畫廊站點->e-hentai</string>
|
||||
|
||||
<string name="app_waring">本應用中內容來自互聯網,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用作者不會對由本應用造成的任何後果負責。\n\n未成年人應在監護人指導下使用本應用。</string>
|
||||
<string name="error_username_cannot_empty">用户名不可為空</string>
|
||||
@@ -428,7 +425,6 @@
|
||||
<string name="default_favorites_collection">默認收藏夾</string>
|
||||
<string name="let_me_select">讓我選擇</string>
|
||||
<string name="collections">收藏夾</string>
|
||||
<string name="need_sign_in">需要登錄</string>
|
||||
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">被玩壞了</string>
|
||||
@@ -465,7 +461,6 @@
|
||||
<string name="no_archives">沒有壓縮包</string>
|
||||
<string name="download_archive_started">開始下載壓縮包</string>
|
||||
<string name="download_archive_failure">無法下載壓縮包</string>
|
||||
<string name="archive">壓縮包</string>
|
||||
<string name="download_archive_failure_no_hath">下載壓縮包需要 H@H 客户端</string>
|
||||
<string name="settings_privacy">隱私</string>
|
||||
<string name="settings_privacy_secure">不允許荧幕抓取</string>
|
||||
@@ -496,6 +491,9 @@
|
||||
<string name="pref_color_filter_mode">濾鏡融合機制</string>
|
||||
<string name="filter_mode_multiply">色彩增值</string>
|
||||
<string name="filter_mode_screen">濾色</string>
|
||||
<string name="filter_mode_overlay">覆蓋</string>
|
||||
<string name="filter_mode_lighten">減淡/變亮</string>
|
||||
<string name="filter_mode_darken">加深/變暗</string>
|
||||
<string name="pref_read_with_long_tap">長按顯示</string>
|
||||
<string name="pref_reader_theme">背景顏色</string>
|
||||
<string name="white_background">白色</string>
|
||||
|
||||
@@ -54,15 +54,16 @@
|
||||
<string name="error_cant_create_temp_file">無法建立暫存檔案</string>
|
||||
<string name="error_cant_save_image">無法儲存圖片</string>
|
||||
<string name="error_invalid_number">無效的數字</string>
|
||||
<string name="error_empty_html">頁面內容為空</string>
|
||||
<string name="kokomade_tip">設定->Eh->圖庫站台->e-hentai</string>
|
||||
<string name="app_waring">本應用程式中內容來自網際網路,部分內容可能對您的生理及心理造成難以恢復的傷害。本應用程式的作者不會對由其所造成的任何影響負責。\n\n未成年人應在監護人指導下使用本應用程式。\n\n繼續使用即代表您同意上述條款。</string>
|
||||
<string name="app_waring_2">繼續使用即表示您同意上述條款。</string>
|
||||
<string name="error_username_cannot_empty">使用者名稱欄位不能為空</string>
|
||||
<string name="error_password_cannot_empty">密碼欄位不能為空</string>
|
||||
<string name="tourist_mode">略過登入</string>
|
||||
<string name="sign_in_failed">登入失敗</string>
|
||||
<string name="sign_in_failed_tip">若持續出錯,請嘗試“通過網頁登入”或“通過 Cookie 登入”。</string>
|
||||
<string name="get_it">知道了</string>
|
||||
<string name="cloudflare_bypass_failed">未能繞過 Cloudflare 驗證</string>
|
||||
<string name="open_in_webview">在 WebView 中開啟網址以完成驗證?</string>
|
||||
<!-- Select scene -->
|
||||
<string name="select_scene">你想選擇哪個圖庫站台?</string>
|
||||
<string name="select_scene_explain">e-hentai:對任何人開放\nexhentai:僅對登入使用者開放</string>
|
||||
@@ -144,7 +145,11 @@
|
||||
<string name="no_more_previews">已經顯示所有預覽了</string>
|
||||
<string name="more_previews">更多預覽</string>
|
||||
<string name="refresh">重新整理</string>
|
||||
<string name="refresh_original">重新整理 (原圖)</string>
|
||||
<string name="open_in_other_app">在其他應用程式中開啟</string>
|
||||
<string name="clear_image_cache">清除圖片快取</string>
|
||||
<string name="clear_image_cache_confirm">為該圖庫清除所有快取圖片?</string>
|
||||
<string name="image_cache_cleared">已清除圖片快取</string>
|
||||
<string name="rate_successfully">評分成功</string>
|
||||
<string name="rate_failed">評分失敗</string>
|
||||
<string name="no_torrents">沒有種子</string>
|
||||
@@ -322,6 +327,8 @@
|
||||
<string name="settings_eh_thumb_resolution">縮圖解析度</string>
|
||||
<string name="settings_eh_thumb_resolution_summary">%s,指定解析度可能會使縮圖取得失敗</string>
|
||||
<string name="settings_eh_thumb_resolution_auto">自動</string>
|
||||
<string name="settings_eh_force_eh_thumb">強制 e-hentai 縮圖/API 伺服器</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">若exhentai載入遇到問題可嘗試此項\n注意,此項將使用 e-hentai 設定來載入圖片。</string>
|
||||
<string name="settings_eh_show_jpn_title">顯示日文標題</string>
|
||||
<string name="settings_eh_show_jpn_title_summary">需同時在 EHentai 網站設定中啟用 Japanese Title</string>
|
||||
<string name="settings_eh_show_gallery_pages">顯示圖庫頁數</string>
|
||||
@@ -370,6 +377,8 @@
|
||||
<string name="settings_advanced_dump_logcat_to">已將紀錄檔儲存至 %s</string>
|
||||
<string name="settings_advanced_read_cache_size">閱讀快取大小</string>
|
||||
<string name="settings_advanced_app_language_title">應用程式介面語言</string>
|
||||
<string name="settings_advanced_cronet_integration">[實驗性] Cronet 整合</string>
|
||||
<string name="settings_advanced_cronet_integration_summary">使用 Cronet 下載圖片 (性能較佳)</string>
|
||||
<string name="settings_advanced_export_data">匯出檔案</string>
|
||||
<string name="settings_advanced_export_data_summary">儲存資料至外接儲存設備,例如下載列表,快速搜尋列表</string>
|
||||
<string name="settings_advanced_export_data_to">已匯出資料至 %s</string>
|
||||
@@ -391,6 +400,7 @@
|
||||
<string name="settings_about_version">版本號碼</string>
|
||||
<string name="settings_about_build_time">於 %s 建置</string>
|
||||
<string name="settings_about_check_for_updates">檢查更新</string>
|
||||
<string name="license">License</string>
|
||||
<!-- ExcludedLanguagesScene -->
|
||||
<string name="please_wait">請稍候</string>
|
||||
<string name="cant_read_the_file">無法讀取檔案</string>
|
||||
@@ -407,9 +417,10 @@
|
||||
<string name="delete_favorites_dialog_message">將 %d 項收藏移除?</string>
|
||||
<string name="move_favorites_dialog_title">移動收藏</string>
|
||||
<string name="default_favorites_collection">預設收藏夾</string>
|
||||
<string name="default_favorites_warning">若啟用此項將無法添加收藏備註</string>
|
||||
<string name="let_me_select">讓我每次都選擇</string>
|
||||
<string name="favorite_note">收藏備註</string>
|
||||
<string name="collections">收藏夾</string>
|
||||
<string name="need_sign_in">需要登入</string>
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">被玩壞了</string>
|
||||
<!-- Guide -->
|
||||
@@ -442,7 +453,6 @@
|
||||
<string name="no_archives">沒有壓縮檔</string>
|
||||
<string name="download_archive_started">開始下載壓縮檔</string>
|
||||
<string name="download_archive_failure">下載失敗</string>
|
||||
<string name="archive">壓縮檔</string>
|
||||
<string name="archive_free">免費</string>
|
||||
<string name="archive_original">原始檔案</string>
|
||||
<string name="archive_resample">重新採樣檔案</string>
|
||||
@@ -457,14 +467,21 @@
|
||||
<string name="settings_privacy">隱私</string>
|
||||
<string name="settings_privacy_secure">不允許螢幕擷取</string>
|
||||
<string name="settings_privacy_secure_summary">啟用後,將無法對本程式進行螢幕擷取,同時,將不會在系統任務切換器中顯示該程式的預覽內容,重新啟動本程式以套用此變更</string>
|
||||
<string name="clear_search_history">清除裝置上的搜尋記錄</string>
|
||||
<string name="clear_search_history_summary">移除曾在此裝置上進行過的搜尋</string>
|
||||
<string name="clear_search_history_confirm">清除搜尋記錄?</string>
|
||||
<string name="search_history_cleared">已清除搜尋記錄</string>
|
||||
<string name="download_service">下載服務</string>
|
||||
<string name="favorite_name">收藏圖庫</string>
|
||||
<string name="black_dark_theme">純黑深色主題</string>
|
||||
<string name="harmonize_category_color">以動態顏色為分類顏色配色</string>
|
||||
<string name="use_ci_update_channel">使用 CI 更新頻道</string>
|
||||
<string name="download_filter">過濾</string>
|
||||
<string name="download_all">全部</string>
|
||||
<string name="download_start_all_reversed">全部開始(倒序)</string>
|
||||
<string name="settings_download_download_delay">下載延時</string>
|
||||
<string name="settings_download_download_delay_summary">每次下載延時 %s 毫秒</string>
|
||||
<string name="settings_download_download_timeout">下載超時(秒)</string>
|
||||
<string name="no_browser_installed">請安裝一個瀏覽器。</string>
|
||||
<string name="toplist_alltime">從始至終</string>
|
||||
<string name="toplist_pastyear">過去一年</string>
|
||||
@@ -504,6 +521,16 @@
|
||||
<string name="passwd_cannot_be_empty">密碼不能為空</string>
|
||||
<string name="list_tile_thumb_size">詳情模式下縮圖大小</string>
|
||||
<string name="account_name">帳號</string>
|
||||
<string name="preload_thumb_aggressively">積極地預載入縮圖</string>
|
||||
<string name="auto_updates">自動檢查更新</string>
|
||||
<string name="update_frequency_never">從不</string>
|
||||
<string name="update_frequency_daily">每天</string>
|
||||
<string name="update_frequency_3days">每3天</string>
|
||||
<string name="update_frequency_weekly">每週</string>
|
||||
<string name="update_frequency_biweekly">每兩週</string>
|
||||
<string name="update_frequency_monthly">每月</string>
|
||||
<string name="new_version_available">有新版本可用!</string>
|
||||
<string name="already_latest_version">已是最新版本</string>
|
||||
<!-- Tachiyomi Reader Start-->
|
||||
<string name="action_settings">設定</string>
|
||||
<string name="action_retry">重試</string>
|
||||
@@ -554,6 +581,9 @@
|
||||
<string name="pref_color_filter_mode">濾鏡融合機制</string>
|
||||
<string name="filter_mode_multiply">色彩增值</string>
|
||||
<string name="filter_mode_screen">濾色</string>
|
||||
<string name="filter_mode_overlay">覆蓋</string>
|
||||
<string name="filter_mode_lighten">減淡/變亮</string>
|
||||
<string name="filter_mode_darken">加深/變暗</string>
|
||||
<string name="pref_cutout_short">使用螢幕凹口區域</string>
|
||||
<string name="action_menu">選單</string>
|
||||
<string name="webtoon_side_padding_25">25%</string>
|
||||
@@ -594,5 +624,9 @@
|
||||
<string name="disabled_nav">停用</string>
|
||||
<string name="wide_color_gamut">使用 Display P3 色彩空間</string>
|
||||
<string name="settings_eh_request_news_timepicker">設定請求新聞頁面的時間</string>
|
||||
<string name="dark_theme">深色主題</string>
|
||||
<string name="dark_theme_follow_system">跟隨系統</string>
|
||||
<string name="dark_theme_off">總是關閉</string>
|
||||
<string name="dark_theme_on">總是開啟</string>
|
||||
<!-- Tachiyomi Reader End-->
|
||||
</resources>
|
||||
@@ -16,6 +16,20 @@
|
||||
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<string-array name="toplist_entries" translatable="false">
|
||||
<item>@string/toplist_alltime</item>
|
||||
<item>@string/toplist_pastyear</item>
|
||||
<item>@string/toplist_pastmonth</item>
|
||||
<item>@string/toplist_yesterday</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="toplist_values" translatable="false">
|
||||
<item>11</item>
|
||||
<item>12</item>
|
||||
<item>13</item>
|
||||
<item>15</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="list_mode_entries" translatable="false">
|
||||
<item>@string/settings_eh_list_mode_detail</item>
|
||||
<item>@string/settings_eh_list_mode_thumb</item>
|
||||
|
||||
@@ -33,45 +33,26 @@
|
||||
<dimen name="rating_size">16dp</dimen>
|
||||
<dimen name="rating_interval">1dp</dimen>
|
||||
|
||||
<!-- 4dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_interval">12dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_margin_h">12dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_list_margin_v">4dp</dimen>
|
||||
|
||||
<!-- 4dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_grid_interval">4dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_grid_margin_h">12dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_grid_margin_v">4dp</dimen>
|
||||
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<!-- 12dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="gallery_search_bar_margin_v">10dp</dimen>
|
||||
|
||||
<dimen name="gallery_padding_top_search_bar">64dp</dimen>
|
||||
<dimen name="gallery_padding_top_search_bar">80dp</dimen>
|
||||
|
||||
<dimen name="gallery_list_column_width_long">480dp</dimen>
|
||||
<dimen name="gallery_list_column_width_short">320dp</dimen>
|
||||
|
||||
<!-- 4dp - 4dp, 4dp for card padding -->
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="search_layout_margin_h">4dp</dimen>
|
||||
<!-- 8dp - 4dp, 4dp for card padding -->
|
||||
<dimen name="search_layout_margin_v">4dp</dimen>
|
||||
|
||||
<!-- DrawerArrowDrawable -->
|
||||
|
||||
<!-- AddDeleteDrawable -->
|
||||
<dimen name="add_size">20dp</dimen>
|
||||
<dimen name="add_thickness">2dp</dimen>
|
||||
|
||||
<!-- CategoryTable -->
|
||||
|
||||
<!-- AdvanceSearchTable -->
|
||||
|
||||
<!-- Search item -->
|
||||
<dimen name="search_category_padding_h">@dimen/keyline_margin</dimen>
|
||||
<dimen name="search_category_padding_v">@dimen/keyline_margin</dimen>
|
||||
@@ -81,29 +62,19 @@
|
||||
|
||||
<dimen name="strip_item_padding_v">@dimen/strip_item_padding</dimen>
|
||||
|
||||
<!-- Switch padding -->
|
||||
|
||||
<!-- FabLayout -->
|
||||
<dimen name="fab_layout_primary_margin">24dp</dimen>
|
||||
<dimen name="fab_layout_secondary_margin">16dp</dimen>
|
||||
<dimen name="fab_size">56dp</dimen>
|
||||
<dimen name="fab_min_size">40dp</dimen>
|
||||
|
||||
<dimen name="fab_rv_padding_size">80dp</dimen>
|
||||
|
||||
<!-- Slider -->
|
||||
<dimen name="fab_rv_padding_size">64dp</dimen>
|
||||
|
||||
<dimen name="corner_fab_margin">16dp</dimen>
|
||||
|
||||
<dimen name="gallery_detail_thumb_width">128dp</dimen>
|
||||
<dimen name="gallery_detail_thumb_height">192dp</dimen>
|
||||
|
||||
<!-- Lock pattern view -->
|
||||
|
||||
<!-- Custom preference -->
|
||||
|
||||
<!-- Gallery -->
|
||||
|
||||
<dimen name="bottom_sheet_width">0dp</dimen>
|
||||
<dimen name="screen_edge_margin">16dp</dimen>
|
||||
|
||||
|
||||
@@ -21,13 +21,8 @@
|
||||
<string name="pd_play" translatable="false">M8,5.14V19.14L19,12.14L8,5.14Z</string>
|
||||
<string name="pd_check_all" translatable="false">M0.41,13.41L6,19L7.41,17.58L1.83,12M22.24,5.58L11.66,16.17L7.5,12L6.07,13.41L11.66,19L23.66,7M18,7L16.59,5.58L10.24,11.93L11.66,13.34L18,7Z</string>
|
||||
<string name="pd_folder_move" translatable="false">M9,18V15H5V11H9V8L14,13M20,6H12L10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6Z</string>
|
||||
<string name="pd_settings" translatable="false">M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z</string>
|
||||
<string name="pd_plus" translatable="false">M21,13H13V21H11V13H3V11H11V3H13V11H21V13Z</string>
|
||||
<string name="pd_download" translatable="false">M5,20H19V18H5M19,9H15V3H9V9H5L12,16L19,9Z</string>
|
||||
<string name="pd_homepage" translatable="false">M10,20V14H14V20H19V12H22L12,3L2,12H5V20H10Z</string>
|
||||
<string name="pd_fire" translatable="false">M11.71,19C9.93,19 8.5,17.59 8.5,15.86C8.5,14.24 9.53,13.1 11.3,12.74C13.07,12.38 14.9,11.53 15.92,10.16C16.31,11.45 16.5,12.81 16.5,14.2C16.5,16.84 14.36,19 11.71,19M13.5,0.67C13.5,0.67 14.24,3.32 14.24,5.47C14.24,7.53 12.89,9.2 10.83,9.2C8.76,9.2 7.2,7.53 7.2,5.47L7.23,5.1C5.21,7.5 4,10.61 4,14A8,8 0 0,0 12,22A8,8 0 0,0 20,14C20,8.6 17.41,3.8 13.5,0.67Z</string>
|
||||
<string name="pd_heart" translatable="false">M12,21.35L10.55,20.03C5.4,15.36 2,12.27 2,8.5C2,5.41 4.42,3 7.5,3C9.24,3 10.91,3.81 12,5.08C13.09,3.81 14.76,3 16.5,3C19.58,3 22,5.41 22,8.5C22,12.27 18.6,15.36 13.45,20.03L12,21.35Z</string>
|
||||
<string name="pd_history" translatable="false">M11,7V12.11L15.71,14.9L16.5,13.62L12.5,11.25V7M12.5,2C8.97,2 5.91,3.92 4.27,6.77L2,4.5V11H8.5L5.75,8.25C6.96,5.73 9.5,4 12.5,4A7.5,7.5 0 0,1 20,11.5A7.5,7.5 0 0,1 12.5,19C9.23,19 6.47,16.91 5.44,14H3.34C4.44,18.03 8.11,21 12.5,21C17.74,21 22,16.75 22,11.5A9.5,9.5 0 0,0 12.5,2Z</string>
|
||||
<string name="pd_delete" translatable="false">M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z</string>
|
||||
<string name="pd_go_to" translatable="false">M13,13L17,17L21,13H18V10C18,6.1,14.9,3,11,3S4,6.1,4,10V17H6V10C6,7.2,8.2,5,11,5S16,7.2,16,10V13H13M,16,19H18V21H16V19Z</string>
|
||||
<string name="pd_magnify" translatable="false">M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z</string>
|
||||
@@ -38,10 +33,7 @@
|
||||
<string name="pd_download_box" translatable="false">M19,3H5C3.9,3,3,3.9,3,5V19C3,20.1,3.9,21,5,21H19C20.1,21,21,20.1,21,19V5C21,3.9,20.1,3,19,3ZM12,17L7,12H10V8H14V12H17L12,17Z</string>
|
||||
<string name="pd_refresh" translatable="false">M17.65,6.35C16.2,4.9 14.21,4 12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20C15.73,20 18.84,17.45 19.73,14H17.65C16.83,16.33 14.61,18 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6C13.66,6 15.14,6.69 16.22,7.78L13,11H20V4L17.65,6.35Z</string>
|
||||
<string name="pd_file_image" translatable="false">M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M6,20H15L18,20V12L14,16L12,14L6,20M8,9A2,2 0 0,0 6,11A2,2 0 0,0 8,13A2,2 0 0,0 10,11A2,2 0 0,0 8,9Z</string>
|
||||
<string name="pd_eh_subscription" translatable="false">M20,8H4V6h16V8 M18,2H6v2h12V2 M20,10H4c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2h16c1.1,0,2-0.9,2-2v-8C22,10.9,21.1,10,20,10zM11,14H7v1h4v2H7v1h4v2H5v-8h6V14z M19,20h-2v-3h-2v3h-2v-8h2v3h2v-3h2V20z</string>
|
||||
<string name="pd_pencil" translatable="false">M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z</string>
|
||||
<string name="pd_big_history" translatable="false">M5.5,2H22V0H5C3.3,0,2,1.3,2,3V21C2,22.7,3.3,24,5,24H22V4H5.5C4.1,4,4.1,2,5.5,2ZM20,6V22H6V6H20M12.5,11.8V14.4L14.9,15.8L15.3,15.2L13.3,14V11.9M13.3,9.3C11.5,9.3,10,10.3,9.2,11.7L8,10.5V13.8H11.3L9.9,12.4C10.5,11.1,11.8,10.3,13.3,10.3C15.4,10.3,17.1,12,17.1,14.1S15.4,17.9,13.3,17.9C11.7,17.9,10.3,16.9,9.8,15.4H8.7C9.3,17.4,11.1,18.9,13.3,18.9C15.9,18.9,18.1,16.8,18.1,14.1C18,11.4,15.9,9.3,13.3,9.3Z</string>
|
||||
<string name="pd_big_download" translatable="false">M0,19V24H24V19H0ZM16,22H15V21H16V22ZM19,22H18V21H19V22ZM22,22H21V21H22V22M15,11L15,0L9,0L9,11L5,11L12,18L19,11Z</string>
|
||||
<string name="pd_sad_panda" translatable="false">M21.1,7.8C22.2,7.4,23,6.3,23,5C23,3.3,21.7,2,20,2C18.9,2,17.9,2.6,17.4,3.6L21.1,7.8M6.6,3.6C6.1,2.6,5.1,2,4,2C2.3,2,1,3.3,1,5C1,6.3,1.8,7.4,2.9,7.8L6.6,3.6M10.1,13L8,15.1C7.4,15.7,6.5,15.7,5.9,15.1L5.9,15.1C5.3,14.5,5.3,13.6,5.9,13L8,10.9C8.6,10.3,9.5,10.3,10.1,10.9L10.1,10.9C10.7,11.5,10.7,12.4,10.1,13M16,15.1L13.9,13C13.3,12.4,13.3,11.5,13.9,10.9L13.9,10.9C14.5,10.3,15.4,10.3,16,10.9L18.1,13C18.7,13.6,18.7,14.5,18.1,15.1L18.1,15.1C17.5,15.7,16.6,15.7,16,15.1M12,17C12.8,17,13.5,17.4,13.5,18S12.8,19,12,19S10.5,18.6,10.5,18S11.2,17,12,17Z</string>
|
||||
|
||||
</resources>
|
||||
@@ -36,7 +36,6 @@
|
||||
<string name="history">History</string>
|
||||
<string name="downloads">Downloads</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="stub" translatable="false">Stub</string>
|
||||
<string name="username">Username</string>
|
||||
<string name="password">Password</string>
|
||||
<string name="sign_in">Sign in</string>
|
||||
@@ -93,8 +92,6 @@
|
||||
<string name="error_cant_create_temp_file">Couldn\'t create temp file</string>
|
||||
<string name="error_cant_save_image">Can\'t save image</string>
|
||||
<string name="error_invalid_number">Invalid number</string>
|
||||
<string name="error_empty_html">Empty page</string>
|
||||
<string name="kokomade_tip">Settings->Eh->Gallery Site->e-hentai</string>
|
||||
<string name="app_waring">The content of this application is from the Internet. Some of it may do physical or mental harm to you. You have learnt the risks above and would like to undertake them. By continuing to use it, you agree to the above terms.</string>
|
||||
<string name="app_waring_2">By continuing to use it, you agree to the above terms.</string>
|
||||
<string name="error_username_cannot_empty">Username cannot be an empty string</string>
|
||||
@@ -372,13 +369,15 @@
|
||||
<string name="settings_eh_thumb_resolution_summary">Currently %s. Certain resolutions might cause getting thumb failed</string>
|
||||
<string name="settings_eh_thumb_resolution_auto">Auto</string>
|
||||
<string name="settings_eh_force_eh_thumb">Force e-hentai thumb/API server</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">Try this if you have trouble loading from exhentai</string>
|
||||
<string name="settings_eh_force_eh_thumb_summary">Try this if you have trouble loading from exhentai\nNote this will use e-hentai settings to load images</string>
|
||||
<string name="settings_eh_show_jpn_title">Show Japanese title</string>
|
||||
<string name="settings_eh_show_jpn_title_summary">Require enabling Japanese Title in Settings on EHentai website</string>
|
||||
<string name="settings_eh_show_gallery_pages">Show gallery pages</string>
|
||||
<string name="settings_eh_show_gallery_pages_summary">Display the number of pages in the gallery list</string>
|
||||
<string name="settings_eh_show_gallery_comments">Show gallery comments</string>
|
||||
<string name="settings_eh_show_gallery_comments_summary">Show comments on the gallery details page</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold">Comment score threshold</string>
|
||||
<string name="settings_eh_show_gallery_comment_threshold_summary">Hide comments at or below this score (-101 disables)</string>
|
||||
<string name="settings_eh_show_tag_translations">Show tag translations</string>
|
||||
<string name="settings_eh_show_tag_translations_summary">Show tag translations instead of the original text (It takes time to download the data file)</string>
|
||||
<string name="settings_eh_tag_translations_source">Placeholder</string>
|
||||
@@ -462,7 +461,7 @@
|
||||
<string name="app_language_th" translatable="false">ไทย</string>
|
||||
<string name="app_language_zh_cn" translatable="false">中文(大陆)</string>
|
||||
<string name="app_language_zh_hk" translatable="false">中文(香港)</string>
|
||||
<string name="app_language_zh_tw" translatable="false">國文(臺灣)</string>
|
||||
<string name="app_language_zh_tw" translatable="false">中文(臺灣)</string>
|
||||
<string name="app_language_nb_no" translatable="false">Norwegian Bokmål</string>
|
||||
<!-- ExcludedLanguagesScene -->
|
||||
<string name="please_wait">Please wait</string>
|
||||
@@ -482,7 +481,6 @@
|
||||
<string name="let_me_select">Let me select</string>
|
||||
<string name="favorite_note">Favorite Note</string>
|
||||
<string name="collections">Collections</string>
|
||||
<string name="need_sign_in">Signing in is required</string>
|
||||
<!-- Progress Scene -->
|
||||
<string name="error_something_wrong_happened">Something wrong happened</string>
|
||||
<!-- Guide -->
|
||||
@@ -522,7 +520,6 @@
|
||||
<string name="no_archives">No Archives</string>
|
||||
<string name="download_archive_started">Archive download started</string>
|
||||
<string name="download_archive_failure">Failed to download archive</string>
|
||||
<string name="archive">Archive</string>
|
||||
<string name="archive_free">Free</string>
|
||||
<string name="archive_original">Original archive</string>
|
||||
<string name="archive_resample">Resample archive</string>
|
||||
@@ -543,6 +540,7 @@
|
||||
<string name="search_history_cleared">Search history cleared</string>
|
||||
<string name="download_service">Download Service</string>
|
||||
<string name="favorite_name">Favorite</string>
|
||||
<string name="dark_theme">Dark theme</string>
|
||||
<string name="black_dark_theme">Black dark theme</string>
|
||||
<string name="harmonize_category_color">Harmonize category color for Dynamic Color</string>
|
||||
<string name="download_filter">Filter</string>
|
||||
@@ -589,6 +587,7 @@
|
||||
<string name="list_tile_thumb_size">Thumb size in thumb mode</string>
|
||||
<string name="account_name">Account</string>
|
||||
<string name="preload_thumb_aggressively">Preload thumbs aggressively</string>
|
||||
<string name="user_agent">User Agent</string>
|
||||
<string name="auto_updates">Automatically check for updates</string>
|
||||
<string name="update_frequency_never">Never</string>
|
||||
<string name="update_frequency_daily">Daily</string>
|
||||
@@ -635,6 +634,9 @@
|
||||
<string name="pref_color_filter_mode">Color filter blend mode</string>
|
||||
<string name="filter_mode_multiply">Multiply</string>
|
||||
<string name="filter_mode_screen">Screen</string>
|
||||
<string name="filter_mode_overlay">Overlay</string>
|
||||
<string name="filter_mode_lighten">Dodge / Lighten</string>
|
||||
<string name="filter_mode_darken">Burn / Darken</string>
|
||||
<string name="pref_keep_screen_on">Keep screen on</string>
|
||||
<string name="pref_read_with_tapping_inverted">Invert tap zones</string>
|
||||
<string name="tapping_inverted_none">None</string>
|
||||
@@ -688,5 +690,8 @@
|
||||
<string name="action_save_to">Save to…</string>
|
||||
<string name="wide_color_gamut">Use Display P3 color space</string>
|
||||
<string name="settings_eh_request_news_timepicker">Set time to request news</string>
|
||||
<string name="dark_theme_follow_system">Follow system</string>
|
||||
<string name="dark_theme_off">Always off</string>
|
||||
<string name="dark_theme_on">Always on</string>
|
||||
<!-- Tachiyomi Reader End-->
|
||||
</resources>
|
||||
@@ -29,11 +29,9 @@
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
||||
<!-- Transparent Status Bar -->
|
||||
<item name="android:windowLightStatusBar">?isLightTheme</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
|
||||
<!-- Transparent Navigation Bar -->
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">?isLightTheme</item>
|
||||
<item name="android:navigationBarColor">@android:color/transparent</item>
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="q">false</item>
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
| 变种 | 功能 |
|
||||
|-|-|
|
||||
| Default | Android 9+, 完全支持 |
|
||||
| Marshmallow | Android 6.0+, 有限支持* |
|
||||
| Marshmallow | Android 6.0-8.1, 有限支持* |
|
||||
| OSS | FOSS 依赖项** |
|
||||
| GMS | 专有 GMS 组件 |
|
||||
|
||||
|
||||
Loaded 100 of 102 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user