Compare commits

..

13 Commits
dev ... master

Author SHA1 Message Date
tom5079
0eb6d40d7e fix tag 2025-11-14 19:09:46 -08:00
tom5079
f7b9260a2b Merge pull request #189 from contigo/patch-1
Update strings.xml (zh-rTW)
2025-11-14 19:07:16 -08:00
contigo
7df09aa74b Update strings.xml (zh-rTW)
</string> 가 없는 2개 문자열을 수정
2025-10-15 11:42:01 +09:00
tom5079
b3ce36f81c Merge pull request #160 from Regu-Miabyss/master
Add Chinese(Taiwan) translation
2025-10-09 10:49:25 -07:00
tom5079
27dded11c1 Merge pull request #188 from EmiyaSyahriel/apptl-id
add Indonesian App UI translation
2025-10-09 10:49:01 -07:00
Syahriel Ibnu Irfansyah
758210658e change: polish indonesian translation a bit 2025-10-08 16:17:55 +07:00
Syahriel Ibnu Irfansyah
830d490822 change: replace "atur" with "kelola" 2025-10-08 16:15:16 +07:00
Syahriel Ibnu Irfansyah
236ce2c189 add: indonesian translation 2025-10-08 15:58:40 +07:00
tom5079
7f3f17d08e image fix 2025-03-23 10:43:17 -07:00
tom5079
72937cdd42 thumbnail fix 2025-03-23 10:31:12 -07:00
tom5079
9c878f5e44 fix image loading 2025-03-23 10:13:41 -07:00
tom5079
db928a168f fix loading 2025-03-23 09:44:53 -07:00
Regu-Miabyss
a6d5336608 Add Chinese(Taiwan) translation 2024-07-19 13:36:02 +08:00
19 changed files with 716 additions and 606 deletions

View File

@@ -19,7 +19,7 @@ android {
minSdk = 21 minSdk = 21
targetSdk = 35 targetSdk = 35
versionCode = 70 versionCode = 70
versionName = "5.3.18" versionName = "5.3.22"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
} }
@@ -69,22 +69,7 @@ dependencies {
implementation(libs.kotlinx.serialization.json) implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime) implementation(libs.kotlinx.datetime)
implementation(platform(libs.compose.bom)) implementation(libs.androidx.compose.runtime)
androidTestImplementation(platform(libs.compose.bom))
implementation(libs.compose.material3)
implementation(libs.compose.ui)
implementation(libs.compose.ui.tooling.preview)
debugImplementation(libs.compose.ui.tooling)
androidTestImplementation(libs.compose.ui.test)
debugImplementation(libs.compose.ui.test.manifest)
implementation(libs.compose.icons)
implementation(libs.compose.adaptive)
implementation(libs.compose.activity)
implementation(libs.compose.lifecycle.viewmodel)
implementation(libs.compose.livedata)
implementation(libs.compose.navigation)
implementation(libs.accompanist.adaptive)
implementation(libs.core.ktx) implementation(libs.core.ktx)
implementation(libs.appcompat) implementation(libs.appcompat)

View File

@@ -12,7 +12,7 @@
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 70, "versionCode": 70,
"versionName": "5.3.18", "versionName": "5.3.22",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
], ],

View File

@@ -5,34 +5,22 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="23"/>
android:name="android.permission.READ_EXTERNAL_STORAGE" <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="23"/>
android:maxSdkVersion="23" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="23" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation"
android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation"
tools:targetApi="s" /> tools:targetApi="s" />
<uses-permission <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="32"
android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="32"
tools:ignore="CoarseFineLocation" /> tools:ignore="CoarseFineLocation" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-feature <uses-feature android:name="android.hardware.camera" android:required="false" />
android:name="android.hardware.camera" <uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
android:required="false" />
<uses-feature
android:name="android.hardware.camera.autofocus"
android:required="false" />
<application <application
android:name=".Pupil" android:name=".Pupil"
@@ -40,20 +28,20 @@
android:fullBackupContent="true" android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"
android:roundIcon="@mipmap/ic_launcher_round" android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
tools:ignore="UnusedAttribute" android:networkSecurityConfig="@xml/network_security_config"
tools:replace="android:theme"> tools:replace="android:theme"
tools:ignore="UnusedAttribute">
<meta-data <meta-data
android:name="com.google.mlkit.vision.DEPENDENCIES" android:name="com.google.mlkit.vision.DEPENDENCIES"
android:value="face" /> android:value="face" />
<provider <provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.provider" android:authorities="${applicationId}.provider"
android:name="androidx.core.content.FileProvider"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">
@@ -63,18 +51,15 @@
</provider> </provider>
<service <service android:name=".services.DownloadService"
android:name=".services.DownloadService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service <service android:name=".services.TransferClientService"
android:name=".services.TransferClientService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
<service <service android:name=".services.TransferServerService"
android:name=".services.TransferServerService"
android:exported="false" android:exported="false"
android:foregroundServiceType="dataSync" /> android:foregroundServiceType="dataSync" />
@@ -90,8 +75,8 @@
<activity <activity
android:name=".ui.ReaderActivity" android:name=".ui.ReaderActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true" android:parentActivityName=".ui.MainActivity"
android:parentActivityName=".ui.MainComposeActivity"> android:exported="true">
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -192,13 +177,13 @@
</activity> </activity>
<activity <activity
android:name=".ui.SettingsActivity" android:name=".ui.SettingsActivity"
android:label="@string/settings_title" /> android:label="@string/settings_title">
</activity>
<activity <activity
android:name=".ui.MainComposeActivity" android:name=".ui.MainActivity"
android:configChanges="keyboardHidden|orientation|screenSize" android:configChanges="keyboardHidden|orientation|screenSize"
android:exported="true" android:theme="@style/NoActionBarAppTheme"
android:theme="@android:style/Theme.Material.Light.NoActionBar" android:exported="true">
android:windowSoftInputMode="adjustResize">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@@ -206,6 +191,7 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
<activity android:name=".ui.TransferActivity" /> <activity android:name=".ui.TransferActivity" />
</application> </application>

View File

@@ -26,6 +26,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@@ -37,22 +38,22 @@ import com.google.android.gms.common.GooglePlayServicesRepairableException
import com.google.android.gms.security.ProviderInstaller import com.google.android.gms.security.ProviderInstaller
import com.google.firebase.FirebaseApp import com.google.firebase.FirebaseApp
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.*
import okhttp3.Dispatcher import okhttp3.Dispatcher
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Response import okhttp3.Response
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.pupil.hitomi.evaluationContext
import xyz.quaver.pupil.hitomi.readText
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.*
import xyz.quaver.pupil.util.SavedSet
import xyz.quaver.pupil.util.getProxyInfo
import xyz.quaver.pupil.util.preferences
import xyz.quaver.pupil.util.proxyInfo
import java.io.File import java.io.File
import java.net.URL
import java.security.KeyStore import java.security.KeyStore
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.CertificateFactory import java.security.cert.CertificateFactory
import java.util.UUID import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.net.ssl.SSLContext import javax.net.ssl.SSLContext
@@ -93,19 +94,16 @@ fun getSSLContext(context: Context): SSLContext {
keyStore.setCertificateEntry("isrgrootx1", certificate) keyStore.setCertificateEntry("isrgrootx1", certificate)
val defaultTrustManagerFactory = val defaultTrustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
defaultTrustManagerFactory.init(null as KeyStore?) defaultTrustManagerFactory.init(null as KeyStore?)
defaultTrustManagerFactory.trustManagers.filterIsInstance<X509TrustManager>() defaultTrustManagerFactory.trustManagers.filterIsInstance(X509TrustManager::class.java).forEach { trustManager ->
.forEach { trustManager ->
trustManager.acceptedIssuers.forEach { acceptedIssuer -> trustManager.acceptedIssuers.forEach { acceptedIssuer ->
keyStore.setCertificateEntry(acceptedIssuer.subjectDN.name, acceptedIssuer) keyStore.setCertificateEntry(acceptedIssuer.subjectDN.name, acceptedIssuer)
} }
} }
val trustManagerFactory = val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
trustManagerFactory.init(keyStore) trustManagerFactory.init(keyStore)
val sslContext = SSLContext.getInstance("TLS") val sslContext = SSLContext.getInstance("TLS")
@@ -144,10 +142,7 @@ class Pupil : Application() {
.proxyInfo(proxyInfo) .proxyInfo(proxyInfo)
.addInterceptor { chain -> .addInterceptor { chain ->
val request = chain.request().newBuilder() val request = chain.request().newBuilder()
.addHeader( .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36")
"User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.81 Safari/537.36"
)
.header("Referer", "https://hitomi.la/") .header("Referer", "https://hitomi.la/")
.build() .build()
@@ -183,8 +178,7 @@ class Pupil : Application() {
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0) histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0) favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
favoriteTags = favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "") searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
favoriteTags.filter { it.tag.contains('_') }.forEach { favoriteTags.filter { it.tag.contains('_') }.forEach {
@@ -215,60 +209,35 @@ class Pupil : Application() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.createNotificationChannel( manager.createNotificationChannel(NotificationChannel("download", getString(R.string.channel_download), NotificationManager.IMPORTANCE_LOW).apply {
NotificationChannel(
"download",
getString(R.string.channel_download),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_download_description) description = getString(R.string.channel_download_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel( manager.createNotificationChannel(NotificationChannel("downloader", getString(R.string.channel_downloader), NotificationManager.IMPORTANCE_LOW).apply {
NotificationChannel(
"downloader",
getString(R.string.channel_downloader),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_downloader_description) description = getString(R.string.channel_downloader_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel( manager.createNotificationChannel(NotificationChannel("update", getString(R.string.channel_update), NotificationManager.IMPORTANCE_HIGH).apply {
NotificationChannel(
"update",
getString(R.string.channel_update),
NotificationManager.IMPORTANCE_HIGH
).apply {
description = getString(R.string.channel_update_description) description = getString(R.string.channel_update_description)
enableLights(true) enableLights(true)
enableVibration(true) enableVibration(true)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel( manager.createNotificationChannel(NotificationChannel("import", getString(R.string.channel_update), NotificationManager.IMPORTANCE_LOW).apply {
NotificationChannel(
"import",
getString(R.string.channel_update),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_update_description) description = getString(R.string.channel_update_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
lockscreenVisibility = Notification.VISIBILITY_SECRET lockscreenVisibility = Notification.VISIBILITY_SECRET
}) })
manager.createNotificationChannel( manager.createNotificationChannel(NotificationChannel("transfer", getString(R.string.channel_transfer), NotificationManager.IMPORTANCE_LOW).apply {
NotificationChannel(
"transfer",
getString(R.string.channel_transfer),
NotificationManager.IMPORTANCE_LOW
).apply {
description = getString(R.string.channel_transfer_description) description = getString(R.string.channel_transfer_description)
enableLights(false) enableLights(false)
enableVibration(false) enableVibration(false)
@@ -276,12 +245,10 @@ class Pupil : Application() {
}) })
} }
AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.setDefaultNightMode(when (Preferences.get<Boolean>("dark_mode")) {
when (Preferences.get<Boolean>("dark_mode")) {
true -> AppCompatDelegate.MODE_NIGHT_YES true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO false -> AppCompatDelegate.MODE_NIGHT_NO
} })
)
super.onCreate() super.onCreate()
} }

View File

@@ -16,12 +16,14 @@
package xyz.quaver.pupil.hitomi package xyz.quaver.pupil.hitomi
import kotlinx.coroutines.* import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock.System.now import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@@ -30,9 +32,7 @@ import okhttp3.Response
import xyz.quaver.pupil.client import xyz.quaver.pupil.client
import java.io.IOException import java.io.IOException
import java.net.URL import java.net.URL
import java.util.concurrent.Executors
import kotlin.coroutines.resumeWithException import kotlin.coroutines.resumeWithException
import kotlin.time.Duration.Companion.minutes
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
const val protocol = "https:" const val protocol = "https:"
@@ -40,25 +40,25 @@ const val protocol = "https:"
@Serializable @Serializable
data class Artist( data class Artist(
val artist: String, val artist: String,
val url: String val url: String,
) )
@Serializable @Serializable
data class Group( data class Group(
val group: String, val group: String,
val url: String val url: String,
) )
@Serializable @Serializable
data class Parody( data class Parody(
val parody: String, val parody: String,
val url: String val url: String,
) )
@Serializable @Serializable
data class Character( data class Character(
val character: String, val character: String,
val url: String val url: String,
) )
@Serializable @Serializable
@@ -66,7 +66,7 @@ data class Tag(
val tag: String, val tag: String,
val url: String, val url: String,
val female: String? = null, val female: String? = null,
val male: String? = null val male: String? = null,
) )
@Serializable @Serializable
@@ -74,7 +74,7 @@ data class Language(
val galleryid: String, val galleryid: String,
val url: String, val url: String,
val language_localname: String, val language_localname: String,
val name: String val name: String,
) )
@Serializable @Serializable
@@ -93,7 +93,7 @@ data class GalleryInfo(
val languages: List<Language> = emptyList(), val languages: List<Language> = emptyList(),
val characters: List<Character>? = null, val characters: List<Character>? = null,
val scene_indexes: List<Int>? = emptyList(), val scene_indexes: List<Int>? = emptyList(),
val files: List<GalleryFiles> = emptyList() val files: List<GalleryFiles> = emptyList(),
) )
val json = Json { val json = Json {
@@ -104,13 +104,16 @@ val json = Json {
} }
typealias HeaderSetter = (Request.Builder) -> Request.Builder typealias HeaderSetter = (Request.Builder) -> Request.Builder
fun URL.readText(settings: HeaderSetter? = null): String { fun URL.readText(settings: HeaderSetter? = null): String {
val request = Request.Builder() val request = Request.Builder()
.url(this).let { .url(this).let {
settings?.invoke(it) ?: it settings?.invoke(it) ?: it
}.build() }.build()
return client.newCall(request).execute().also{ if (it.code() != 200) throw IOException("CODE ${it.code()}") }.body()?.use { it.string() } ?: throw IOException() return client.newCall(request).execute()
.also { if (it.code() != 200) throw IOException("CODE ${it.code()}") }.body()
?.use { it.string() } ?: throw IOException()
} }
fun URL.readBytes(settings: HeaderSetter? = null): ByteArray { fun URL.readBytes(settings: HeaderSetter? = null): ByteArray {
@@ -119,7 +122,9 @@ fun URL.readBytes(settings: HeaderSetter? = null): ByteArray {
settings?.invoke(it) ?: it settings?.invoke(it) ?: it
}.build() }.build()
return client.newCall(request).execute().also { if (it.code() != 200) throw IOException("CODE ${it.code()}") }.body()?.use { it.bytes() } ?: throw IOException() return client.newCall(request).execute()
.also { if (it.code() != 200) throw IOException("CODE ${it.code()}") }.body()
?.use { it.bytes() } ?: throw IOException()
} }
@Suppress("EXPERIMENTAL_API_USAGE") @Suppress("EXPERIMENTAL_API_USAGE")
@@ -130,7 +135,7 @@ fun getGalleryInfo(galleryID: Int) =
) )
//common.js //common.js
const val domain = "ltn.hitomi.la" const val domain = "ltn.gold-usergeneratedcontent.net"
const val galleryblockextension = ".html" const val galleryblockextension = ".html"
const val galleryblockdir = "galleryblock" const val galleryblockdir = "galleryblock"
const val nozomiextension = ".nozomi" const val nozomiextension = ".nozomi"
@@ -152,7 +157,11 @@ object gg {
mutex.withLock { mutex.withLock {
if (lastRetrieval == null || (lastRetrieval!! + 60000) < System.currentTimeMillis()) { if (lastRetrieval == null || (lastRetrieval!! + 60000) < System.currentTimeMillis()) {
val ggjs: String = suspendCancellableCoroutine { continuation -> val ggjs: String = suspendCancellableCoroutine { continuation ->
val call = client.newCall(Request.Builder().url("https://ltn.hitomi.la/gg.js").build()) val call =
client.newCall(
Request.Builder().url("https://ltn.gold-usergeneratedcontent.net/gg.js")
.build()
)
call.enqueue(object : Callback { call.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
@@ -202,34 +211,46 @@ object gg {
refresh() refresh()
return b return b
} }
fun s(h: String): String { fun s(h: String): String {
val m = Regex("(..)(.)$").find(h) val m = Regex("(..)(.)$").find(h)
return m!!.groupValues.let { it[2] + it[1] }.toInt(16).toString(10) return m!!.groupValues.let { it[2] + it[1] }.toInt(16).toString(10)
} }
} }
suspend fun subdomainFromURL(url: String, base: String? = null) : String { suspend fun subdomainFromURL(url: String, base: String? = null, dir: String? = null): String {
var retval = "b" var retval = ""
if (!base.isNullOrBlank()) if (base.isNullOrBlank()) {
retval = base when {
dir == "webp" -> retval = "w"
dir == "avif" -> retval = "a"
}
}
val b = 16 val b = 16
val r = Regex("""/[0-9a-f]{61}([0-9a-f]{2})([0-9a-f])""") val r = Regex("""/[0-9a-f]{61}([0-9a-f]{2})([0-9a-f])""")
val m = r.find(url) ?: return "a" val m = r.find(url) ?: return ""
val g = m.groupValues.let { it[2] + it[1] }.toIntOrNull(b) val g = m.groupValues.let { it[2] + it[1] }.toIntOrNull(b)
if (g != null) { if (g != null) {
retval = (97+ gg.m(g)).toChar().toString() + retval retval = if (base.isNullOrEmpty()) {
retval + (1 + gg.m(g)).toString()
} else {
(97 + gg.m(g)).toChar().toString() + base
}
} }
return retval return retval
} }
suspend fun urlFromUrl(url: String, base: String? = null) : String { suspend fun urlFromUrl(url: String, base: String? = null, dir: String? = null): String {
return url.replace(Regex("""//..?\.hitomi\.la/"""), "//${subdomainFromURL(url, base)}.hitomi.la/") return url.replace(
Regex("""//..?\.(?:gold-usergeneratedcontent\.net|hitomi\.la)/"""),
"//${subdomainFromURL(url, base, dir)}.gold-usergeneratedcontent.net/"
)
} }
suspend fun fullPathFromHash(hash: String): String = suspend fun fullPathFromHash(hash: String): String =
@@ -238,28 +259,42 @@ suspend fun fullPathFromHash(hash: String) : String =
fun realFullPathFromHash(hash: String): String = fun realFullPathFromHash(hash: String): String =
hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1/$hash") hash.replace(Regex("""^.*(..)(.)$"""), "$2/$1/$hash")
suspend fun urlFromHash(galleryID: Int, image: GalleryFiles, dir: String? = null, ext: String? = null) : String { suspend fun urlFromHash(
galleryID: Int,
image: GalleryFiles,
dir: String? = null,
ext: String? = null,
): String {
val ext = ext ?: dir ?: image.name.takeLastWhile { it != '.' } val ext = ext ?: dir ?: image.name.takeLastWhile { it != '.' }
val dir = dir ?: "images" return buildString {
return "https://a.hitomi.la/$dir/${fullPathFromHash(image.hash)}.$ext" append("https://a.gold-usergeneratedcontent.net/")
if (dir != "webp" && dir != "avif") {
append(dir)
append("/")
}
append(fullPathFromHash(image.hash))
append(".")
append(ext)
}
} }
suspend fun urlFromUrlFromHash(galleryID: Int, image: GalleryFiles, dir: String? = null, ext: String? = null, base: String? = null) = suspend fun urlFromUrlFromHash(
galleryID: Int,
image: GalleryFiles,
dir: String? = null,
ext: String? = null,
base: String? = null,
) =
if (base == "tn") if (base == "tn")
urlFromUrl("https://a.hitomi.la/$dir/${realFullPathFromHash(image.hash)}.$ext", base) urlFromUrl(
"https://a.gold-usergeneratedcontent.net/$dir/${realFullPathFromHash(image.hash)}.$ext",
base,
)
else else
urlFromUrl(urlFromHash(galleryID, image, dir, ext), base) urlFromUrl(urlFromHash(galleryID, image, dir, ext), base, dir)
suspend fun rewriteTnPaths(html: String) {
html.replace(Regex("""//tn\.hitomi\.la/[^/]+/[0-9a-f]/[0-9a-f]{2}/[0-9a-f]{64}""")) { url ->
runBlocking {
urlFromUrl(url.value, "tn")
}
}
}
suspend fun imageUrlFromImage(galleryID: Int, image: GalleryFiles, noWebp: Boolean): String { suspend fun imageUrlFromImage(galleryID: Int, image: GalleryFiles, noWebp: Boolean): String {
return urlFromUrlFromHash(galleryID, image, "webp", null, "a") return urlFromUrlFromHash(galleryID, image, "webp")
// return when { // return when {
// noWebp -> // noWebp ->
// urlFromUrlFromHash(galleryID, image) // urlFromUrlFromHash(galleryID, image)

View File

@@ -18,63 +18,25 @@
package xyz.quaver.pupil.ui package xyz.quaver.pupil.ui
import android.app.Activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.PersistableBundle import android.os.PersistableBundle
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat import xyz.quaver.pupil.R
import xyz.quaver.pupil.util.LockManager import xyz.quaver.pupil.util.LockManager
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.normalizeID
open class BaseComponentActivity : ComponentActivity() {
private var locked: Boolean = true
private val lockLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK)
locked = false
else
finish()
}
@CallSuper
override fun onCreate(savedInstanceState: Bundle?) {
enableEdgeToEdge()
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
locked = !LockManager(this).locks.isNullOrEmpty()
}
@CallSuper
override fun onResume() {
super.onResume()
if (Preferences["security_mode"])
window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE
)
else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
if (locked)
lockLauncher.launch(Intent(this, LockActivity::class.java))
}
}
open class BaseActivity : AppCompatActivity() { open class BaseActivity : AppCompatActivity() {
private var locked: Boolean = true private var locked: Boolean = true
private val lockLauncher = private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { if (it.resultCode == Activity.RESULT_OK)
if (it.resultCode == RESULT_OK)
locked = false locked = false
else else
finish() finish()
@@ -94,12 +56,12 @@ open class BaseActivity : AppCompatActivity() {
if (Preferences["security_mode"]) if (Preferences["security_mode"])
window.setFlags( window.setFlags(
WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE,
WindowManager.LayoutParams.FLAG_SECURE WindowManager.LayoutParams.FLAG_SECURE)
)
else else
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
if (locked) if (locked)
lockLauncher.launch(Intent(this, LockActivity::class.java)) lockLauncher.launch(Intent(this, LockActivity::class.java))
} }
} }

View File

@@ -1,44 +0,0 @@
package xyz.quaver.pupil.ui
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.adaptive.calculateDisplayFeatures
import xyz.quaver.pupil.ui.compose.MainApp
import xyz.quaver.pupil.ui.theme.AppTheme
import xyz.quaver.pupil.ui.viewmodel.MainViewModel
import xyz.quaver.pupil.util.requestNotificationPermission
import xyz.quaver.pupil.util.showNotificationPermissionExplanationDialog
class MainComposeActivity : BaseComponentActivity() {
private val requestNotificationPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (!isGranted) {
showNotificationPermissionExplanationDialog(this)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestNotificationPermission(this, requestNotificationPermissionLauncher, false)
val viewModel: MainViewModel by viewModels()
setContent {
val displayFeatures = calculateDisplayFeatures(this)
val uiState by viewModel.searchState.collectAsStateWithLifecycle()
AppTheme {
MainApp(
uiState = uiState,
displayFeatures = displayFeatures
)
}
}
}
}

View File

@@ -1,20 +0,0 @@
package xyz.quaver.pupil.ui.compose
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
import androidx.compose.runtime.Composable
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import androidx.window.core.layout.WindowSizeClass
import androidx.window.layout.DisplayFeature
import xyz.quaver.pupil.ui.viewmodel.SearchState
@Composable
fun MainApp(
uiState: SearchState,
displayFeatures: List<DisplayFeature>,
navController: NavController = rememberNavController(),
windowSizeClass: WindowSizeClass = currentWindowAdaptiveInfo().windowSizeClass,
) {
Text("Hello, World!")
}

View File

@@ -1,149 +0,0 @@
package xyz.quaver.pupil.ui.theme
import androidx.compose.ui.graphics.Color
val md_theme_light_primary = Color(0xFF006688)
val md_theme_light_onPrimary = Color(0xFFFFFFFF)
val md_theme_light_primaryContainer = Color(0xFFC2E8FF)
val md_theme_light_onPrimaryContainer = Color(0xFF001E2B)
val md_theme_light_secondary = Color(0xFF4E616D)
val md_theme_light_onSecondary = Color(0xFFFFFFFF)
val md_theme_light_secondaryContainer = Color(0xFFD1E5F3)
val md_theme_light_onSecondaryContainer = Color(0xFF091E28)
val md_theme_light_tertiary = Color(0xFF5F5A7D)
val md_theme_light_onTertiary = Color(0xFFFFFFFF)
val md_theme_light_tertiaryContainer = Color(0xFFE5DEFF)
val md_theme_light_onTertiaryContainer = Color(0xFF1C1736)
val md_theme_light_error = Color(0xFFBA1A1A)
val md_theme_light_errorContainer = Color(0xFFFFDAD6)
val md_theme_light_onError = Color(0xFFFFFFFF)
val md_theme_light_onErrorContainer = Color(0xFF410002)
val md_theme_light_background = Color(0xFFFBFCFE)
val md_theme_light_onBackground = Color(0xFF191C1E)
val md_theme_light_surface = Color(0xFFFBFCFE)
val md_theme_light_onSurface = Color(0xFF191C1E)
val md_theme_light_surfaceVariant = Color(0xFFDCE3E9)
val md_theme_light_onSurfaceVariant = Color(0xFF40484D)
val md_theme_light_outline = Color(0xFF71787D)
val md_theme_light_inverseOnSurface = Color(0xFFF0F1F3)
val md_theme_light_inverseSurface = Color(0xFF2E3133)
val md_theme_light_inversePrimary = Color(0xFF75D1FF)
val md_theme_light_shadow = Color(0xFF000000)
val md_theme_light_surfaceTint = Color(0xFF006688)
val md_theme_light_outlineVariant = Color(0xFFC0C7CD)
val md_theme_light_scrim = Color(0xFF000000)
val md_theme_dark_primary = Color(0xFF75D1FF)
val md_theme_dark_onPrimary = Color(0xFF003548)
val md_theme_dark_primaryContainer = Color(0xFF004D67)
val md_theme_dark_onPrimaryContainer = Color(0xFFC2E8FF)
val md_theme_dark_secondary = Color(0xFFB5C9D7)
val md_theme_dark_onSecondary = Color(0xFF20333D)
val md_theme_dark_secondaryContainer = Color(0xFF364954)
val md_theme_dark_onSecondaryContainer = Color(0xFFD1E5F3)
val md_theme_dark_tertiary = Color(0xFFC9C2EA)
val md_theme_dark_onTertiary = Color(0xFF312C4C)
val md_theme_dark_tertiaryContainer = Color(0xFF474364)
val md_theme_dark_onTertiaryContainer = Color(0xFFE5DEFF)
val md_theme_dark_error = Color(0xFFFFB4AB)
val md_theme_dark_errorContainer = Color(0xFF93000A)
val md_theme_dark_onError = Color(0xFF690005)
val md_theme_dark_onErrorContainer = Color(0xFFFFDAD6)
val md_theme_dark_background = Color(0xFF191C1E)
val md_theme_dark_onBackground = Color(0xFFE1E2E5)
val md_theme_dark_surface = Color(0xFF191C1E)
val md_theme_dark_onSurface = Color(0xFFE1E2E5)
val md_theme_dark_surfaceVariant = Color(0xFF40484D)
val md_theme_dark_onSurfaceVariant = Color(0xFFC0C7CD)
val md_theme_dark_outline = Color(0xFF8A9297)
val md_theme_dark_inverseOnSurface = Color(0xFF191C1E)
val md_theme_dark_inverseSurface = Color(0xFFE1E2E5)
val md_theme_dark_inversePrimary = Color(0xFF006688)
val md_theme_dark_shadow = Color(0xFF000000)
val md_theme_dark_surfaceTint = Color(0xFF75D1FF)
val md_theme_dark_outlineVariant = Color(0xFF40484D)
val md_theme_dark_scrim = Color(0xFF000000)
val seed = Color(0xFF4FC3F7)
val Gray50 = Color(0xFFF9FAFB)
val Gray100 = Color(0xFFF3F4F6)
val Gray200 = Color(0xFFE5E7EB)
val Gray300 = Color(0xFFD1D5DB)
val Gray400 = Color(0xFF9CA3AF)
val Gray500 = Color(0xFF6B7280)
val Gray600 = Color(0xFF4B5563)
val Gray700 = Color(0xFF374151)
val Gray800 = Color(0xFF1F2937)
val Gray900 = Color(0xFF111827)
val Red50 = Color(0xFFFEF2F2)
val Red100 = Color(0xFFFEE2E2)
val Red200 = Color(0xFFFECACA)
val Red300 = Color(0xFFFCA5A5)
val Red400 = Color(0xFFF87171)
val Red500 = Color(0xFFEF4444)
val Red600 = Color(0xFFDC2626)
val Red700 = Color(0xFFB91C1C)
val Red800 = Color(0xFF991B1B)
val Red900 = Color(0xFF7F1D1D)
val Yellow50 = Color(0xFFFFFBEB)
val Yellow100 = Color(0xFFFEF3C7)
val Yellow200 = Color(0xFFFDE68A)
val Yellow300 = Color(0xFFFCD34D)
val Yellow400 = Color(0xFFFBBF24)
val Yellow500 = Color(0xFFF59E0B)
val Yellow600 = Color(0xFFD97706)
val Yellow700 = Color(0xFFB45309)
val Yellow800 = Color(0xFF92400E)
val Yellow900 = Color(0xFF78350F)
val Green50 = Color(0xFFECFDF5)
val Green100 = Color(0xFFD1FAE5)
val Green200 = Color(0xFFA7F3D0)
val Green300 = Color(0xFF6EE7B7)
val Green400 = Color(0xFF34D399)
val Green500 = Color(0xFF10B981)
val Green600 = Color(0xFF059669)
val Green700 = Color(0xFF047857)
val Green800 = Color(0xFF065F46)
val Green900 = Color(0xFF064E3B)
val Blue50 = Color(0xFFEFF6FF)
val Blue100 = Color(0xFFDBEAFE)
val Blue200 = Color(0xFFBFDBFE)
val Blue300 = Color(0xFF93C5FD)
val Blue400 = Color(0xFF60A5FA)
val Blue500 = Color(0xFF3B82F6)
val Blue600 = Color(0xFF2563EB)
val Blue700 = Color(0xFF1D4ED8)
val Blue800 = Color(0xFF1E40AF)
val Blue900 = Color(0xFF1E3A8A)
val Indigo50 = Color(0xFFEEF2FF)
val Indigo100 = Color(0xFFE0E7FF)
val Indigo200 = Color(0xFFC7D2FE)
val Indigo300 = Color(0xFFA5B4FC)
val Indigo400 = Color(0xFF818CF8)
val Indigo500 = Color(0xFF6366F1)
val Indigo600 = Color(0xFF4F46E5)
val Indigo700 = Color(0xFF4338CA)
val Indigo800 = Color(0xFF3730A3)
val Indigo900 = Color(0xFF312E81)
val Purple50 = Color(0xFFF5F3FF)
val Purple100 = Color(0xFFEDE9FE)
val Purple200 = Color(0xFFDDD6FE)
val Purple300 = Color(0xFFC4B5FD)
val Purple400 = Color(0xFFA78BFA)
val Purple500 = Color(0xFF8B5CF6)
val Purple600 = Color(0xFF7C3AED)
val Purple700 = Color(0xFF6D28D9)
val Purple800 = Color(0xFF5B21B6)
val Purple900 = Color(0xFF4C1D95)
val Pink50 = Color(0xFFFDF2F8)
val Pink100 = Color(0xFFFCE7F3)
val Pink200 = Color(0xFFFBCFE8)
val Pink300 = Color(0xFFF9A8D4)
val Pink400 = Color(0xFFF472B6)
val Pink500 = Color(0xFFEC4899)
val Pink600 = Color(0xFFDB2777)
val Pink700 = Color(0xFFBE185D)
val Pink800 = Color(0xFF9D174D)
val Pink900 = Color(0xFF831843)

View File

@@ -1,97 +0,0 @@
package xyz.quaver.pupil.ui.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val LightColors = lightColorScheme(
primary = md_theme_light_primary,
onPrimary = md_theme_light_onPrimary,
primaryContainer = md_theme_light_primaryContainer,
onPrimaryContainer = md_theme_light_onPrimaryContainer,
secondary = md_theme_light_secondary,
onSecondary = md_theme_light_onSecondary,
secondaryContainer = md_theme_light_secondaryContainer,
onSecondaryContainer = md_theme_light_onSecondaryContainer,
tertiary = md_theme_light_tertiary,
onTertiary = md_theme_light_onTertiary,
tertiaryContainer = md_theme_light_tertiaryContainer,
onTertiaryContainer = md_theme_light_onTertiaryContainer,
error = md_theme_light_error,
errorContainer = md_theme_light_errorContainer,
onError = md_theme_light_onError,
onErrorContainer = md_theme_light_onErrorContainer,
background = md_theme_light_background,
onBackground = md_theme_light_onBackground,
surface = md_theme_light_surface,
onSurface = md_theme_light_onSurface,
surfaceVariant = md_theme_light_surfaceVariant,
onSurfaceVariant = md_theme_light_onSurfaceVariant,
outline = md_theme_light_outline,
inverseOnSurface = md_theme_light_inverseOnSurface,
inverseSurface = md_theme_light_inverseSurface,
inversePrimary = md_theme_light_inversePrimary,
surfaceTint = md_theme_light_surfaceTint,
outlineVariant = md_theme_light_outlineVariant,
scrim = md_theme_light_scrim,
)
private val DarkColors = darkColorScheme(
primary = md_theme_dark_primary,
onPrimary = md_theme_dark_onPrimary,
primaryContainer = md_theme_dark_primaryContainer,
onPrimaryContainer = md_theme_dark_onPrimaryContainer,
secondary = md_theme_dark_secondary,
onSecondary = md_theme_dark_onSecondary,
secondaryContainer = md_theme_dark_secondaryContainer,
onSecondaryContainer = md_theme_dark_onSecondaryContainer,
tertiary = md_theme_dark_tertiary,
onTertiary = md_theme_dark_onTertiary,
tertiaryContainer = md_theme_dark_tertiaryContainer,
onTertiaryContainer = md_theme_dark_onTertiaryContainer,
error = md_theme_dark_error,
errorContainer = md_theme_dark_errorContainer,
onError = md_theme_dark_onError,
onErrorContainer = md_theme_dark_onErrorContainer,
background = md_theme_dark_background,
onBackground = md_theme_dark_onBackground,
surface = md_theme_dark_surface,
onSurface = md_theme_dark_onSurface,
surfaceVariant = md_theme_dark_surfaceVariant,
onSurfaceVariant = md_theme_dark_onSurfaceVariant,
outline = md_theme_dark_outline,
inverseOnSurface = md_theme_dark_inverseOnSurface,
inverseSurface = md_theme_dark_inverseSurface,
inversePrimary = md_theme_dark_inversePrimary,
surfaceTint = md_theme_dark_surfaceTint,
outlineVariant = md_theme_dark_outlineVariant,
scrim = md_theme_dark_scrim,
)
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable() () -> Unit,
) {
val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
val colors = when {
dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
darkTheme -> DarkColors
else -> LightColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}

View File

@@ -1,12 +0,0 @@
package xyz.quaver.pupil.ui.viewmodel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class MainViewModel : ViewModel() {
private val _uiState = MutableStateFlow(SearchState())
val searchState: StateFlow<SearchState> = _uiState
}
data class SearchState(val stub: String = "")

View File

@@ -24,23 +24,21 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.util.Log
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.*
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.hitomi.GalleryBlock import xyz.quaver.pupil.hitomi.GalleryBlock
import xyz.quaver.pupil.hitomi.GalleryInfo import xyz.quaver.pupil.hitomi.GalleryInfo
import xyz.quaver.pupil.hitomi.imageUrlFromImage import xyz.quaver.pupil.hitomi.imageUrlFromImage
import java.util.Locale import java.util.*
import kotlin.collections.ArrayList
@OptIn(ExperimentalStdlibApi::class) @OptIn(ExperimentalStdlibApi::class)
fun String.wordCapitalize() : String { fun String.wordCapitalize() : String {
@@ -62,8 +60,7 @@ private val suffix = listOf(
) )
fun byteToString(byte: Long, precision : Int = 1) : String { fun byteToString(byte: Long, precision : Int = 1) : String {
var size = byte.toDouble() var size = byte.toDouble(); var suffixIndex = 0
var suffixIndex = 0
while (size >= 1024) { while (size >= 1024) {
size /= 1024 size /= 1024
@@ -95,7 +92,6 @@ val formatMap = mapOf<String, GalleryBlock.() -> (String)>(
"-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" } "-group-" to { if (groups.isNotEmpty()) groups.joinToString() else "N/A" }
// TODO // TODO
) )
/** /**
* Formats download folder name with given Metadata * Formats download folder name with given Metadata
*/ */
@@ -172,10 +168,7 @@ val JsonElement.content
fun checkNotificationEnabled(context: Context) = fun checkNotificationEnabled(context: Context) =
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
ContextCompat.checkSelfPermission( ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
context,
Manifest.permission.POST_NOTIFICATIONS
) == PackageManager.PERMISSION_GRANTED
fun showNotificationPermissionExplanationDialog(context: Context) { fun showNotificationPermissionExplanationDialog(context: Context) {
AlertDialog.Builder(context) AlertDialog.Builder(context)
@@ -189,16 +182,12 @@ fun requestNotificationPermission(
activity: Activity, activity: Activity,
requestPermissionLauncher: ActivityResultLauncher<String>, requestPermissionLauncher: ActivityResultLauncher<String>,
showRationale: Boolean = true, showRationale: Boolean = true,
ifGranted: () -> Unit = { }, ifGranted: () -> Unit,
) { ) {
when { when {
checkNotificationEnabled(activity) -> ifGranted() checkNotificationEnabled(activity) -> ifGranted()
showRationale && ActivityCompat.shouldShowRequestPermissionRationale( showRationale && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.POST_NOTIFICATIONS) ->
activity,
Manifest.permission.POST_NOTIFICATIONS
) ->
showNotificationPermissionExplanationDialog(activity) showNotificationPermissionExplanationDialog(activity)
else -> else ->
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
} }

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<string-array name="proxy_type">
<item>Tanpa Proxy</item>
<item>HTTP</item>
<item>SOCKS</item>
</string-array>
</resources>

View File

@@ -0,0 +1,236 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="warning">Peringatan</string>
<string name="error">Kesalahan</string>
<string name="ignore">Abaikan</string>
<string name="unlimited">Tidak Terbatas</string>
<string name="copied_to_clipboard">Disalin ke papan klip</string>
<string name="channel_download">Unduh</string>
<string name="channel_download_description">Tampilkan status unduhan</string>
<string name="channel_downloader">Pengunduh</string>
<string name="channel_downloader_description">Tampilkan status pengunduh</string>
<string name="channel_update">Perbarui</string>
<string name="channel_update_description">Tampilkan kemajuan pembaruan</string>
<string name="channel_transfer">Transfer</string>
<string name="channel_transfer_description">Tampilkan kemajuan transfer data ke perangkat lain</string>
<string name="unable_to_connect">Gagal tersambung ke hitomi.la</string>
<string name="lock_corrupted">Berkas kunci rusak! Dimohon untuk menginstal ulang Pupil</string>
<string name="main_no_result">Hasil kosong</string>
<string name="unaccessible_download_folder">Dari Android 11 dan yang lebih baru, folder Unduhan yang sekarang tidak akan bisa diakses dari aplikasi luar. Pindahkan folder Unduhan?</string>
<string name="notification_denied">Izin memunculkan notifikasi dibutuhkan untuk melakukan unduhan latar belakang. Jika anda menolak notifikasi dari aplikasi ini, pembaruan dari dalam aplikasi dan unduhan latar belakang akan dinonaktifkan.</string>
<string name="main_drawer_home">Beranda</string>
<string name="main_drawer_history">Riwayat</string>
<string name="main_drawer_downloads">Diunduh</string>
<string name="main_drawer_favorite">Favorit</string>
<string name="main_drawer_group_contact_title">Kontak</string>
<string name="main_drawer_group_contact_help">Bantuan</string>
<string name="main_drawer_group_contact_homepage">Kunjungi situs apl.</string>
<string name="main_drawer_group_contact_github">Kunjungi github</string>
<string name="main_drawer_group_contact_email">Email saya!</string>
<string name="main_drawer_grouop_contact_discord">Discord</string>
<string name="main_menu_thin">Mode Tipis</string>
<string name="main_menu_sort">Urutan</string>
<string name="main_menu_sort_date_added">Tanggal Ditambahkan</string>
<string name="main_menu_sort_date_published">Tanggal Publikasi</string>
<string name="main_menu_sort_popular_today">Populer: Hari Ini</string>
<string name="main_menu_sort_popular_week">Populer: Minggu Ini</string>
<string name="main_menu_sort_popular_month">Populer: Bulan Ini</string>
<string name="main_menu_sort_popular_year">Populer: Tahun ini</string>
<string name="main_menu_sort_random">Acak</string>
<string name="main_jump_title">Lompat ke Halaman</string>
<string name="main_jump_message">Sekarang halaman: %1$d\nHalaman paling belakang: %2$d</string>
<string name="main_open_gallery_by_id">Buka Galeri dari ID</string>
<string name="reader_failed_to_find_gallery">Gagal membuka galeri</string>
<string name="main_fab_random">Buka galeri acak</string>
<string name="main_fab_cancel">Batalkan semua unduhan</string>
<string name="main_move_to_page">Pergi ke halaman %1$d</string>
<string name="main_download">UNDUH</string>
<string name="main_delete">HAPUS</string>
<string name="update_title">Pembaruan tersedia</string>
<string name="update_download_completed">Unduhan Pembaruan Selesai</string>
<string name="update_download_completed_description">Tap disini untuh memperbarui</string>
<string name="update_notification_description">Mengunduh pembaruan&#8230;</string>
<string name="update_release_note"># Catatan rilis (v%1$s)\n%2$s</string>
<string name="search_hint">Pencarian galeri</string>
<string name="search_all">Mencari seluruh galeri</string>
<string name="search_show_histories">Tampilkan riwayat</string>
<string name="search_show_tags">Tampilkan tag favorit</string>
<string name="gallery_details">Rincian</string>
<string name="gallery_thumbnails">Thumbnail</string>
<string name="gallery_related">Galeri Terkait</string>
<string name="gallery_artists">Seniman</string>
<string name="gallery_groups">Grup</string>
<string name="gallery_language">Bahasa</string>
<string name="gallery_series">Seri</string>
<string name="gallery_characters">Karakter</string>
<string name="gallery_tags">Tag</string>
<string name="galleryblock_series">Seri: %1$s</string>
<string name="galleryblock_type">Jenis: %1$s</string>
<string name="galleryblock_language">Bahasa: %1$s</string>
<!-- READER -->
<string name="reader_loading">Memuat</string>
<string name="reader_go_to_page">Ke halaman</string>
<string name="reader_fab_fullscreen">Layar penuh</string>>
<string name="reader_fab_retry">Coba lagi</string>
<string name="reader_fab_auto">Gulir dengan kedipan mata</string>
<string name="reader_fab_auto_cancel">Berhenti gulir dengan kedipan mata</string>
<string name="reader_fab_download">Unduhan latar belakang</string>
<string name="reader_fab_download_cancel">Batalkan unduhan latar belakang</string>
<string name="reader_notification_text">Mengunduh&#8230;</string>
<string name="reader_notification_complete">Unduhan selesai</string>
<string name="camera_denied">Deteksi kedipan mata tidak bisa berfungsi tanpa izin kamera</string>
<string name="no_camera">Tidak terdeteksi kamera depan di perangkat ini</string>
<!-- DOWNLOADER -->
<string name="downloader_running">Pengunduh dimulai…</string>
<!-- SETTINGS -->
<string name="settings_title">Pengaturan</string>
<string name="settings_app_version_title">Versi aplikasi (Tap untuk memeriksa pembaruan)</string>
<string name="settings_app_version_description">v%s</string>
<string name="settings_beta">Perbarui dari kanal beta</string>
<!-- SEARCH -->
<string name="settings_search_title">Pengaturan pencarian</string>
<string name="settings_galleries_per_page">Galeri per halaman</string>
<string name="settings_default_query">Kueri pencarian default</string>
<!-- SETTINGS/STORAGE -->
<string name="settings_storage">Penyimpanan</string>
<!-- SETTINGS/STORAGE / MANAGE STORAGE -->
<string name="settings_manage_storage">Kelola Penyimpanan</string>
<string name="settings_storage_usage">Sedang menggunakan %s</string>
<string name="settings_storage_usage_loading">Menghitung penggunaan penyimpanan…</string>
<string name="settings_clear_cache">Bersihkan cache</string>
<string name="settings_clear_cache_alert_message">Menghapus cache bisa mempengaruhi kecepatan memuat gambar, lanjutkan?</string>
<string name="settings_recover_downloads">Buat ulang daftar terunduh</string>
<string name="settings_clear_downloads">Bersihkan daftar unduhan</string>
<string name="settings_clear_downloads_alert_message">Ini akan menghapus semua hasil unduhan.\nLanjutkan?</string>
<string name="settings_clear_history">Hapus riwayat</string>
<string name="settings_clear_history_alert_message">Ini akan menghapus seluruh isi riwayat, lanjutkan?</string>
<string name="settings_clear_history_summary">%1$d entri riwayat disimpan</string>
<!-- SETTINGS/STORAGE / MISCELLANEOUS -->
<string name="settings_download_folder_name">Pola nama folder</string>
<string name="settings_invalid_download_folder_name">Pola nama folder mengandung karakter yang tidak sah.</string>
<string name="settings_download_folder_name_message">%s akan digantikan dengan nilai yang disesuaikan\n\n%s</string>
<string name="settings_download_folder">Folder unduhan</string>
<string name="settings_download_folder_removable">Penyimpanan Lepasan</string>
<string name="settings_download_folder_internal">Penyimpanan Internal</string>
<string name="settings_download_folder_available">%s tersedia</string>
<string name="settings_download_folder_custom">Lokasi Kustom</string>
<string name="settings_download_folder_not_writable">Folder tidak dapat ditulisi. Silahkan pilih yang lain.</string>
<string name="settings_cache_limit">Batas Besar Cache</string>
<string name="settings_nomedia_title">Sembunyikan gambar dari galeri</string>
<string name="settings_low_quality">Gambar kualitas rendah</string>
<string name="settings_low_quality_summary">Muat versi gambar kualitas rendah untuk menghemat waktu muat dan penggunaan data</string>
<string name="settings_transfer_data">Transfer data ke perangkat lain</string>
<!-- SETTINGS/APP LOCK -->
<string name="settings_app_lock">Kunci Aplikasi</string>
<string name="settings_app_lock_type">Jenis kunci aplikasi</string>
<!-- SETTINGS/NETWORKING -->
<string name="settings_networking">Jaringan</string>
<string name="settings_mirror_summary">Muat gambar dari jaringan mirror</string>
<string name="settings_proxy_title">Proxy</string>
<string name="settings_max_concurrent_download">Jumlah Unduhan Bersamaan</string>
<!-- SETTINGS/MISCELLANEOUS -->
<string name="settings_miscellaneous_title">Lain-Lain</string>
<string name="settings_tag_translation">Bahasa Tag</string>
<string name="settings_tag_translation_message">Berpartisipasi dalam terjemahan di GitHub</string>
<string name="settings_rtl">Membalik halaman dari kanan-ke-kiri</string>
<string name="settings_security_mode_title">Nyalakan mode kemananan</string>
<string name="settings_security_mode_summary">Buat tampilan tidak bisa dilihat dari aplikasi baru-baru ini dan tangkapan layar.</string>
<string name="settings_dark_mode_title">Tema gelap</string>
<string name="settings_dark_mode_summary">Jaga matamu dari sinar terang layar.</string>
<string name="settings_import_old_galleries">Impor galeri lama</string>
<string name="settings_user_id">ID Pengguna</string>
<string name="settings_oss">Info Sumber Terbuka</string>
<!-- MANAGE FAVORITES -->
<string name="settings_manage_favorites">Kelola favorit</string>
<string name="settings_backup_title">Cadangkan favorit</string>
<string name="settings_backup_failed">Gagal Mengunggah</string>
<string name="settings_backup_share">Bagikan Cadangan</string>
<string name="settings_backup_file_created">Berkas cadangan dibuat</string>
<string name="settings_restore_title">Kembalikan favorit</string>
<string name="settings_restore_failed">Gagal mengembalikan</string>
<string name="settings_restore_success">%1$d entri dikembalikan</string>
<!-- SETTINGS/APP LOCK ACTIVITY -->
<string name="settings_lock_none">Tidak ada</string>
<string name="settings_lock_pattern">Pola</string>
<string name="settings_lock_password">Kata Sandi</string>
<string name="settings_lock_biometrics">Biometrik</string>
<string name="settings_lock_fingerprint">Sidik Jari</string>
<string name="settings_lock_fingerprint_without_lock">Sidik Jari hanya bisa dipakai jika jenis kunci lain juga aktif</string>
<string name="settings_lock_enabled">Aktif</string>
<string name="settings_lock_confirm">Masukkan kunci yang sama sekali lagi untuk mengkonfirmasi</string>
<string name="settings_lock_remove_message">Hilangkan kunci?</string>
<string name="settings_lock_wrong_confirm">Kunci berbeda dari sebelumnya, silahkan coba lagi.</string>
<string name="settings_lock_fingerprint_prompt">Kunci Sidik Jari Pupil™</string>
<string name="settings_lock_fingerprint_prompt_subtitle">Kami perlu bukti bahwa anda memang ras terkuat dibumi.</string>
<!-- SETTINGS/DEFAULT QUERY DIALOG -->
<string name="default_query_dialog_title">Kelola kueri pencarian default</string>
<string name="default_query_dialog_language">Bahasa: </string>
<string name="default_query_dialog_filter_BL">Jangan tampilkan BL</string>
<string name="default_query_dialog_filter_guro">Jangan tampilkan Guro</string>
<string name="default_query_dialog_filter_loli">Saya bukan pedo</string>
<string name="default_query_dialog_language_selector_none">Apapun</string>
<string name="settings_mirror_title">Mirror</string>
<!-- PROXY DIALOG -->
<string name="proxy_dialog_type">Jenis</string>
<string name="proxy_dialog_addr_hint">Alamat</string>
<string name="proxy_dialog_username_hint">Nama Pengguna</string>
<string name="proxy_dialog_password_hint">Kata Sandi</string>
<string name="proxy_dialog_error">Nilai salah</string>
<string name="proxy_dialog_server">Server</string>
<!-- IMPORT OLD GALLERIES -->
<string name="import_old_galleries_folder_not_readable">Folder ini tidak bisa dibaca</string>
<string name="import_old_galleries_notification">Mengimpor galeri lama…</string>
<string name="import_old_galleries_notification_done">Impor selesai</string>
</resources>

View File

@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079
~
~ This program 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.
~
~ This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string-array name="proxy_type">
<item>直接連線</item>
<item>HTTP</item>
<item>SOCKS</item>
</string-array>
</resources>

View File

@@ -0,0 +1,236 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Translate needed down here -->
<string name="warning">警告</string>
<string name="error">錯誤</string>
<string name="ignore">忽略</string>
<string name="unlimited">無限制</string>
<string name="copied_to_clipboard">已複製到剪貼簿</string>
<string name="channel_download">下載</string>
<string name="channel_download_description">展示下載狀態</string>
<string name="channel_downloader">下載器</string>
<string name="channel_downloader_description">顯示下載器狀態</string>
<string name="channel_update">更新</string>
<string name="channel_update_description">顯示更新狀態</string>
<string name="channel_transfer">轉送</string>
<string name="channel_transfer_description">顯示轉送數據到其他裝置的進度</string>
<string name="unable_to_connect">沒法與hitomi.la建立連線</string>
<string name="lock_corrupted">檔案鎖發生錯誤請重裝Pupil</string>
<string name="main_no_result">空無一物</string>
<string name="unaccessible_download_folder">從Android11及以後默認的下載資料夾沒法被外界的應用訪問。是否要改變下載目的地</string>
<string name="notification_denied">在背景下載需要通知權限。如果拒絕此權限則應用內更新和背景下載功能都會被停用。</string>
<string name="main_drawer_home">首頁</string>
<string name="main_drawer_history">歷史記錄</string>
<string name="main_drawer_downloads">下載</string>
<string name="main_drawer_favorite">收藏</string>
<string name="main_drawer_group_contact_title">聯絡</string>
<string name="main_drawer_group_contact_help">幫助</string>
<string name="main_drawer_group_contact_homepage">造訪主頁</string>
<string name="main_drawer_group_contact_github">造訪GitHub</string>
<string name="main_drawer_group_contact_email">給我傳電郵!</string>
<string name="main_drawer_grouop_contact_discord">Discord</string>
<string name="main_menu_thin">緊緻模式</string>
<string name="main_menu_sort">排序</string>
<string name="main_menu_sort_newest">最近新增</string>
<string name="main_menu_sort_popular">最有人氣</string>
<string name="main_jump_title">跳到頁面</string>
<string name="main_jump_message">現在頁面: %1$d\n最大頁面: %2$d</string>
<string name="main_open_gallery_by_id">以ID開啟相簿</string>
<string name="reader_failed_to_find_gallery">無法開啟相簿</string>
<string name="main_fab_random">隨機相簿</string>
<string name="main_fab_cancel">取消所有下載作業</string>
<string name="main_move_to_page">移動到第 %1$d 頁</string>
<string name="main_download">下載</string>
<string name="main_delete">刪除</string>
<string name="update_title">有可用更新</string>
<string name="update_download_completed">下載已完成</string>
<string name="update_download_completed_description">按此更新</string>
<string name="update_notification_description">正下載更新&#8230;</string>
<string name="update_release_note"># Release附註(v%1$s)\n%2$s</string>
<string name="search_hint">搜尋相簿</string>
<string name="search_all">搜尋所有相簿</string>
<string name="search_show_histories">顯示歷史</string>
<string name="search_show_tags">顯示收藏的標籤</string>
<string name="gallery_details">詳情</string>
<string name="gallery_thumbnails">縮圖</string>
<string name="gallery_related">有關聯的相簿</string>
<string name="gallery_artists">藝術家</string>
<string name="gallery_groups">團體</string>
<string name="gallery_language">語言</string>
<string name="gallery_series">系列</string>
<string name="gallery_characters">角色</string>
<string name="gallery_tags">標籤</string>
<string name="galleryblock_series">系列: %1$s</string>
<string name="galleryblock_type">類別: %1$s</string>
<string name="galleryblock_language">語言: %1$s</string>
<string name="galleryblock_pagecount" translatable="false">%dP</string>
<!-- READER -->
<string name="reader_loading">加載中</string>
<string name="reader_go_to_page">轉到頁面</string>
<string name="reader_fab_fullscreen">全螢幕</string>>
<string name="reader_fab_retry">重試</string>
<string name="reader_fab_auto">眨眼捲動</string>
<string name="reader_fab_auto_cancel">眨眼停止捲動</string>
<string name="reader_fab_download">背景下載</string>
<string name="reader_fab_download_cancel">取消背景下載</string>
<string name="reader_notification_text">正下載&#8230;</string>
<string name="reader_notification_complete">下載完成</string>
<string name="camera_denied">權限遭到拒絕,眨眼捲動無法運作。</string>
<string name="no_camera">這臺裝置沒有前置攝像頭。</string>
<!-- DOWNLOADER -->
<string name="downloader_running">下載器運行中…</string>
<!-- SETTINGS -->
<string name="settings_title">設定</string>
<string name="settings_app_version_title">應用版本(點選以更新)</string>
<string name="settings_app_version_description">v%s</string>
<string name="settings_beta">從Beta頻道更新</string>
<!-- SEARCH -->
<string name="settings_search_title">搜尋設定</string>
<string name="settings_galleries_per_page">每頁展示相簿數量</string>
<string name="settings_default_query">預設查詢</string>
<!-- SETTINGS/STORAGE -->
<string name="settings_storage">存儲位置</string>
<!-- SETTINGS/STORAGE / MANAGE STORAGE -->
<string name="settings_manage_storage">管理存儲位置</string>
<string name="settings_storage_usage">現正使用 %s</string>
<string name="settings_storage_usage_loading">計算存儲用量…</string>
<string name="settings_clear_cache">清除快取</string>
<string name="settings_clear_cache_alert_message">快取清除後會影響影像載入速度,確定嗎?</string>
<string name="settings_recover_downloads">重建下載資料庫</string>
<string name="settings_clear_downloads">清除下載</string>
<string name="settings_clear_downloads_alert_message">這會刪掉所有下載相簿。\n你要繼續嗎</string>
<string name="settings_clear_history">清除歷史記錄</string>
<string name="settings_clear_history_alert_message">你想清除歷史記錄嗎?</string>
<string name="settings_clear_history_summary">有 %1$d 條歷史記錄</string>
<!-- SETTINGS/STORAGE / MISCELLANEOUS -->
<string name="settings_download_folder_name">資料夾命名模式</string>
<string name="settings_invalid_download_folder_name">有無效的字元!</string>
<string name="settings_download_folder_name_message">%s 會被替換成對應的字元\n\n%s</string>
<string name="settings_download_folder">下載資料夾</string>
<string name="settings_download_folder_removable">行動儲存</string>
<string name="settings_download_folder_internal">內置儲存</string>
<string name="settings_download_folder_available">%s 可用</string>
<string name="settings_download_folder_custom">自訂位置</string>
<string name="settings_download_folder_not_writable">此資料夾為只讀。請重新選擇另一個。</string>
<string name="settings_cache_limit">快取上限</string>
<string name="settings_nomedia_title">在相簿中隱藏影像</string>
<string name="settings_low_quality">低質影像</string>
<string name="settings_low_quality_summary">載入低質影像以改善載入速度和流量用量。</string>
<string name="settings_transfer_data">轉移數據到其他裝置</string>
<!-- SETTINGS/APP LOCK -->
<string name="settings_app_lock">應用鎖</string>
<string name="settings_app_lock_type">鎖定方式</string>
<!-- SETTINGS/NETWORKING -->
<string name="settings_networking">網路</string>
<string name="settings_mirror_summary">從映像站載入影像</string>
<string name="settings_proxy_title">代理</string>
<string name="settings_max_concurrent_download">並發下載數</string>
<!-- SETTINGS/MISCELLANEOUS -->
<string name="settings_miscellaneous_title">雜項</string>
<string name="settings_tag_translation">標籤語言</string>
<string name="settings_tag_translation_message">在GitHub上貢獻翻譯</string>
<string name="settings_rtl">從右至左翻頁</string>
<string name="settings_security_mode_title">啟用安全模式</string>
<string name="settings_security_mode_summary">啟用安全模式以在最近應用程式列表中隱藏螢幕內容</string>
<string name="settings_dark_mode_title">暗黑模式</string>
<string name="settings_dark_mode_summary">不要讓光亮瞎了你的眼!</string>
<string name="settings_import_old_galleries">匯入以前的相簿</string>
<string name="settings_user_id">用戶ID</string>
<string name="settings_oss">開放原始碼提示</string>
<!-- MANAGE FAVORITES -->
<string name="settings_manage_favorites">管理收藏夾</string>
<string name="settings_backup_title">備份收藏夾</string>
<string name="settings_backup_failed">上載失敗</string>
<string name="settings_backup_share">分享備份</string>
<string name="settings_backup_file_created">備份檔已創建</string>
<string name="settings_restore_title">還原收藏夾</string>
<string name="settings_restore_failed">還原失敗</string>
<string name="settings_restore_success">%1$d 個條目已恢復</string>
<!-- SETTINGS/APP LOCK ACTIVITY -->
<string name="settings_lock_none"></string>
<string name="settings_lock_pattern">圖形</string>
<string name="settings_lock_pin" translatable="false">PIN</string>
<string name="settings_lock_password">密碼</string>
<string name="settings_lock_biometrics">生物辨識</string>
<string name="settings_lock_fingerprint">指紋辨識</string>
<string name="settings_lock_fingerprint_without_lock">只有在啟用其他鎖定方式後才能啟用指紋。</string>
<string name="settings_lock_fingerprint_prompt">Pupil Fingerprint Lock™</string>
<string name="settings_lock_enabled">已啟用</string>
<string name="settings_lock_confirm">重複一次以確認</string>
<string name="settings_lock_remove_message">你想要移除鎖定嗎?</string>
<string name="settings_lock_wrong_confirm">兩次不匹配,請重試。</string>
<!-- SETTINGS/DEFAULT QUERY DIALOG -->
<string name="default_query_dialog_title">設定預設查詢</string>
<string name="default_query_dialog_language">語言: </string>
<string name="default_query_dialog_filter_BL">過濾 BL</string>
<string name="default_query_dialog_filter_guro">過濾 Guro</string>
<string name="default_query_dialog_filter_loli">合法18+模式</string>
<string name="default_query_dialog_language_selector_none">任何的</string>
<string name="settings_mirror_title">映像站</string>
<!-- PROXY DIALOG -->
<string name="proxy_dialog_type">類型</string>
<string name="proxy_dialog_addr_hint">位址</string>
<string name="proxy_dialog_port_hint"></string>
<string name="proxy_dialog_username_hint">使用者名稱</string>
<string name="proxy_dialog_password_hint">密碼</string>
<string name="proxy_dialog_error">錯誤的值</string>
<string name="proxy_dialog_server">伺服器</string>
<!-- IMPORT OLD GALLERIES -->
<string name="import_old_galleries_folder_not_readable">無法讀取該資料夾</string>
<string name="import_old_galleries_notification">匯入舊的相簿…</string>
<string name="import_old_galleries_notification_text" translatable="false">%1$d/%2$d</string>
<string name="import_old_galleries_notification_done">匯入完畢</string>
<string name="settings_lock_fingerprint_prompt_subtitle">喔靠,重試一次吧</string>
</resources>

View File

@@ -46,7 +46,6 @@ runner = "1.6.2"
skyfishjyLibrary = "1.0.1" skyfishjyLibrary = "1.0.1"
dotsindicator = "5.1.0" dotsindicator = "5.1.0"
workRuntimeKtx = "2.10.0" workRuntimeKtx = "2.10.0"
material3WindowSizeClassAndroid = "1.3.1"
[libraries] [libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
@@ -102,22 +101,7 @@ dotsindicator = { module = "com.tbuonomo:dotsindicator", version.ref = "dotsindi
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
recyclerview-fastscroller = { module = "com.quiph.ui:recyclerviewfastscroller", version = "1.0.0" } recyclerview-fastscroller = { module = "com.quiph.ui:recyclerviewfastscroller", version = "1.0.0" }
pinlockview = { module = "com.github.aritraroy:pinlockview", version = "2.1.0" } pinlockview = { module = "com.github.aritraroy:pinlockview", version = "2.1.0" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version = "1.7.8" }
compose-bom = { module = "androidx.compose:compose-bom", version = "2025.02.00" }
compose-material3 = { module = "androidx.compose.material3:material3" }
compose-ui = { module = "androidx.compose.ui:ui" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-ui-test = { module = "androidx.compose.ui:ui-test-junit4" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" }
compose-icons = { module = "androidx.compose.material:material-icons-extended" }
compose-adaptive = { module = "androidx.compose.material3.adaptive:adaptive" }
compose-activity = { module = "androidx.activity:activity-compose", version = "1.10.1" }
compose-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.7" }
compose-livedata = { module = "androidx.compose.runtime:runtime-livedata" }
compose-navigation = { module = "androidx.navigation:navigation-compose", version = "2.8.8" }
accompanist-adaptive = { module = "com.google.accompanist:accompanist-adaptive", version = "0.37.2" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }