Compare commits

...

18 Commits

Author SHA1 Message Date
tom5079
038b8e0ac5 fix language search 2025-02-25 00:19:51 -08:00
tom5079
79a4917897 build apk 2025-02-24 23:55:55 -08:00
tom5079
8c8ead5830 improve japanese 2025-02-24 23:47:44 -08:00
tom5079
a3b6b010be build apk 2025-02-24 23:38:14 -08:00
tom5079
8bf936ee20 build apk 2025-02-24 23:38:14 -08:00
tom5079
c69972f289 update README.md 2025-02-24 23:38:14 -08:00
tom5079
05f555bb91 minsdk 2025-02-24 23:38:14 -08:00
tom5079
83d6058f2b sort mode 2025-02-24 23:38:12 -08:00
tom5079
f888535389 migrate to kts 2025-02-24 23:36:02 -08:00
tom5079
0f2336eccf migrate to kts 2025-02-24 23:36:02 -08:00
tom5079
e8443664dc cleanup 2025-02-24 23:36:02 -08:00
tom5079
0f9b6963a6 dependency update 2025-02-24 23:36:02 -08:00
tom5079
6727ac1014 upgrade agp 2025-02-24 23:36:02 -08:00
tom5079
a15e2c30cb upgrade gcp 2025-02-24 23:36:02 -08:00
tom5079
6c603b2bf3 Merge pull request #171 from maboroshin/master
Update strings.xml improve Japanese translation
2025-02-09 11:24:24 -08:00
maboroshin
8146fc8473 Update strings.xml improve Japanese 2025-02-03 09:17:38 +09:00
maboroshin
3eff34e585 Update strings.xml improve Japanese 2025-02-03 08:28:10 +09:00
maboroshin
b24f4b5306 Update strings.xml reorder Japanese 2025-02-03 07:25:29 +09:00
30 changed files with 1196 additions and 843 deletions

View File

@@ -1,26 +1,29 @@
![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true) ![Banner](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/pupil-banner.png?raw=true)
*Pupil, Hitomi.la viewer for Android* *Pupil, Hitomi.la viewer for Android*
![](https://img.shields.io/github/downloads/tom5079/Pupil/total) ![](https://img.shields.io/github/downloads/tom5079/Pupil/total)
[![](https://img.shields.io/github/downloads/tom5079/Pupil/5.3.13/Pupil-v5.3.13.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.3.13/Pupil-v5.3.13.apk) [![](https://img.shields.io/github/downloads/tom5079/Pupil/5.3.17/Pupil-v5.3.17.apk?color=%234fc3f7&label=DOWNLOAD%20APP&style=for-the-badge)](https://github.com/tom5079/Pupil/releases/download/5.3.17/Pupil-v5.3.17.apk)
[![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v) [![](https://discordapp.com/api/guilds/610452916612104194/embed.png?style=banner2)](https://discord.gg/Stj4b5v)
# Features # Features
![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.jpg?raw=true) ![Main Screen](https://github.com/tom5079/Pupil/blob/gh-pages/assets/images/main-screenshot.jpg?raw=true)
# Installation # Installation
Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or Go [Releases page](https://github.com/tom5079/Pupil/releases) and get latest version or
Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean) Visit [github page](https://tom5079.github.io/Pupil/) (only available in Korean)
or Build app yourself or Build app yourself
# Manual # Manual
[Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean. Consider using translator. [Manual](https://tom5079.github.io/Pupil/2019/06/06/manual-kr.html) is only available in Korean.
Consider using translator.
# Contribution # Contribution
Any kind of contribution is appreciated. Feel free to leave PR! Any kind of contribution is appreciated. Feel free to leave PR!
## Tag Translation ## Tag Translation
Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags) Head over to [tags branch](https://github.com/tom5079/Pupil/tree/tags)

View File

@@ -1,145 +0,0 @@
apply plugin: "com.android.application"
apply plugin: "kotlin-android"
apply plugin: "kotlin-kapt"
apply plugin: "kotlin-parcelize"
apply plugin: "kotlinx-serialization"
apply plugin: "com.google.android.gms.oss-licenses-plugin"
if (file("google-services.json").exists()) {
logger.lifecycle("Firebase Enabled")
apply plugin: "com.google.gms.google-services"
apply plugin: "com.google.firebase.crashlytics"
apply plugin: "com.google.firebase.firebase-perf"
} else {
logger.lifecycle("Firebase Disabled")
}
ext {
okhttp_version = "3.12.12"
}
configurations {
all {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
if (details.requested.group == "com.squareup.okhttp3" && details.requested.name == "okhttp") {
// OkHttp drops support before 5.0 since 3.13.0
details.useVersion okhttp_version
}
}
}
}
}
android {
defaultConfig {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
compileSdk 34
targetSdkVersion 34
versionCode 69
versionName "5.3.15"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildTypes {
debug {
defaultConfig.minSdkVersion 21
minifyEnabled false
shrinkResources false
debuggable true
applicationIdSuffix ".debug"
versionNameSuffix "-DEBUG"
ext.enableCrashlytics = false
ext.alwaysUpdateBuildId = false
}
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
}
}
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
freeCompilerArgs += "-Xuse-experimental=kotlin.Experimental"
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar", "*.aar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-datetime:0.3.2"
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.appcompat:appcompat:1.4.1"
implementation "androidx.activity:activity-ktx:1.4.0"
implementation "androidx.fragment:fragment-ktx:1.4.1"
implementation "androidx.preference:preference-ktx:1.2.0"
implementation "androidx.recyclerview:recyclerview:1.2.1"
implementation "androidx.constraintlayout:constraintlayout:2.1.4"
implementation "androidx.gridlayout:gridlayout:1.0.0"
implementation "androidx.biometric:biometric:1.1.0"
implementation "androidx.work:work-runtime-ktx:2.7.1"
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
implementation "com.google.android.material:material:1.11.0"
implementation platform('com.google.firebase:firebase-bom:32.7.0')
implementation "com.google.firebase:firebase-analytics-ktx"
implementation "com.google.firebase:firebase-crashlytics-ktx"
implementation "com.google.firebase:firebase-perf-ktx"
implementation "com.google.android.gms:play-services-oss-licenses:17.0.1"
implementation "com.google.android.gms:play-services-mlkit-face-detection:17.1.0"
implementation "com.github.clans:fab:1.6.4"
//implementation "com.quiph.ui:recyclerviewfastscroller:0.2.1"
implementation 'com.github.piasy:BigImageViewer:1.8.1'
implementation 'com.github.piasy:FrescoImageLoader:1.8.1'
implementation 'com.github.piasy:FrescoImageViewFactory:1.8.1'
implementation 'com.facebook.fresco:imagepipeline-okhttp3:2.6.0'
//noinspection GradleDependency
implementation "com.squareup.okhttp3:okhttp:$okhttp_version"
implementation "io.ktor:ktor-network:2.3.10"
implementation "com.tbuonomo.andrui:viewpagerdotsindicator:4.1.2"
implementation "net.rdrei.android.dirchooser:library:3.2@aar"
implementation "com.gu:option:1.3"
implementation "com.andrognito.patternlockview:patternlockview:1.0.0"
//implementation "com.andrognito.pinlockview:pinlockview:2.1.0"
implementation "ru.noties.markwon:core:3.1.0"
implementation "com.skyfishjy.ripplebackground:library:1.0.1"
implementation "org.jsoup:jsoup:1.14.3"
implementation "xyz.quaver:documentfilex:0.7.2"
implementation "xyz.quaver:floatingsearchview:1.1.7"
testImplementation "junit:junit:4.13.2"
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.1"
androidTestImplementation "androidx.test.ext:junit:1.1.3"
androidTestImplementation "androidx.test:rules:1.4.0"
androidTestImplementation "androidx.test:runner:1.4.0"
androidTestImplementation "androidx.test.espresso:espresso-core:3.4.0"
}

129
app/build.gradle.kts Normal file
View File

@@ -0,0 +1,129 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlinx.serialization)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.gms.oss.licenses)
alias(libs.plugins.gms.google.services)
alias(libs.plugins.firebase.crashlytics)
alias(libs.plugins.firebase.perf)
id("kotlin-parcelize")
}
android {
namespace = "xyz.quaver.pupil"
compileSdk = 35
defaultConfig {
applicationId = "xyz.quaver.pupil"
minSdk = 21
targetSdk = 35
versionCode = 70
versionName = "5.3.17"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
}
buildFeatures {
buildConfig = true
compose = true
}
buildTypes {
debug {
isMinifyEnabled = false
isShrinkResources = false
isDebuggable = true
applicationIdSuffix = ".debug"
versionNameSuffix = "-DEBUG"
extra.apply {
set("enableCrashlytics", false)
set("alwaysUpdateBuildId", false)
}
}
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
buildFeatures {
viewBinding = true
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
dependencies {
implementation(libs.kotlin.stdlib.jdk8)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.serialization.json)
implementation(libs.kotlinx.datetime)
implementation(libs.androidx.compose.runtime)
implementation(libs.core.ktx)
implementation(libs.appcompat)
implementation(libs.activity.ktx)
implementation(libs.fragment.ktx)
implementation(libs.preference.ktx)
implementation(libs.recyclerview)
implementation(libs.constraintlayout)
implementation(libs.gridlayout)
implementation(libs.biometric)
implementation(libs.work.runtime.ktx)
implementation(libs.library)
implementation(libs.material)
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.analytics.ktx)
implementation(libs.firebase.crashlytics.ktx)
implementation(libs.firebase.perf.ktx)
implementation(libs.play.services.oss.licenses)
implementation(libs.play.services.mlkit.face.detection)
implementation(libs.fab)
implementation(libs.bigimageviewer)
implementation(libs.frescoimageloader)
implementation(libs.frescoimageviewfactory)
implementation(libs.imagepipeline.okhttp3)
//noinspection GradleDependency
implementation(libs.okhttp)
implementation(libs.ktor.network)
implementation(libs.dotsindicator)
implementation(libs.pinlockview)
implementation(libs.patternlockview)
implementation(libs.core)
implementation(libs.ripplebackground.library)
implementation(libs.recyclerview.fastscroller)
implementation(libs.jsoup)
implementation(libs.documentfilex)
implementation(libs.floatingsearchview)
testImplementation(libs.junit)
testImplementation(libs.kotlinx.coroutines.test)
androidTestImplementation(libs.ext.junit)
androidTestImplementation(libs.rules)
androidTestImplementation(libs.runner)
androidTestImplementation(libs.espresso.core)
}

Binary file not shown.

Binary file not shown.

View File

@@ -11,10 +11,27 @@
"type": "SINGLE", "type": "SINGLE",
"filters": [], "filters": [],
"attributes": [], "attributes": [],
"versionCode": 69, "versionCode": 70,
"versionName": "5.3.14", "versionName": "5.3.17",
"outputFile": "app-release.apk" "outputFile": "app-release.apk"
} }
], ],
"elementType": "File" "elementType": "File",
"baselineProfiles": [
{
"minApi": 28,
"maxApi": 30,
"baselineProfiles": [
"baselineProfiles/1/app-release.dm"
]
},
{
"minApi": 31,
"maxApi": 2147483647,
"baselineProfiles": [
"baselineProfiles/0/app-release.dm"
]
}
],
"minSdkVersionForDexing": 21
} }

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Pupil, Hitomi.la viewer for Android ~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079 ~ Copyright (C) 2020 tom5079
~ ~
@@ -17,6 +16,4 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<resources xmlns:tools="http://schemas.android.com/tools"> <resources></resources>
<string name="app_name" translatable="false" tools:override="true">Pupil-Debug</string>
</resources>

View File

@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools">
package="xyz.quaver.pupil">
<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" />

View File

@@ -18,9 +18,9 @@ package xyz.quaver.pupil.hitomi
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import java.util.* import java.util.LinkedList
suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int> = coroutineScope { suspend fun doSearch(query: String, sortMode: SortMode): List<Int> = coroutineScope {
val terms = query val terms = query
.trim() .trim()
.replace(Regex("""^\?"""), "") .replace(Regex("""^\?"""), "")
@@ -34,8 +34,8 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
val negativeTerms = LinkedList<String>() val negativeTerms = LinkedList<String>()
for (term in terms) { for (term in terms) {
if (term.matches(Regex("^-.+"))) if (term.startsWith("-"))
negativeTerms.push(term.replace(Regex("^-"), "")) negativeTerms.push(term.substring(1))
else if (term.isNotBlank()) else if (term.isNotBlank())
positiveTerms.push(term) positiveTerms.push(term)
} }
@@ -43,22 +43,25 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
val positiveResults = positiveTerms.map { val positiveResults = positiveTerms.map {
async { async {
runCatching { runCatching {
getGalleryIDsForQuery(it) getGalleryIDsForQuery(it, sortMode)
}.getOrElse { emptySet() } }.getOrElse { emptySet() }
} }
} }
val negativeResults = negativeTerms.mapIndexed { index, it -> val negativeResults = negativeTerms.map {
async { async {
runCatching { runCatching {
getGalleryIDsForQuery(it) getGalleryIDsForQuery(it, sortMode)
}.getOrElse { emptySet() } }.getOrElse { emptySet() }
} }
} }
val results = when { val results = when {
sortByPopularity -> getGalleryIDsFromNozomi(null, "popular", "all") positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(
positiveTerms.isEmpty() -> getGalleryIDsFromNozomi(null, "index", "all") SearchArgs("all", "index", "all"),
sortMode
)
else -> emptySet() else -> emptySet()
}.toMutableSet() }.toMutableSet()
@@ -79,9 +82,13 @@ suspend fun doSearch(query: String, sortByPopularity: Boolean = false) : Set<Int
} }
//negative results //negative results
negativeResults.forEachIndexed { index, it -> negativeResults.forEach {
filterNegative(it.await()) filterNegative(it.await())
} }
results return@coroutineScope if (sortMode != SortMode.RANDOM) {
results.toList()
} else {
results.shuffled()
}
} }

View File

@@ -26,6 +26,54 @@ import java.nio.ByteOrder
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.math.min import kotlin.math.min
data class SearchArgs(
val area: String?,
val tag: String,
val language: String,
) {
companion object {
fun fromQuery(query: String): SearchArgs? {
if (!query.contains(':')) {
return null
}
val (left, right) = query.split(':')
return when (left) {
"male", "female" -> SearchArgs("tag", query, "all")
"language" -> SearchArgs("all", "index", right)
else -> SearchArgs(left, right, "all")
}
}
}
}
enum class SortMode {
DATE_ADDED,
DATE_PUBLISHED,
POPULAR_TODAY,
POPULAR_WEEK,
POPULAR_MONTH,
POPULAR_YEAR,
RANDOM;
val orderBy: String
get() = when (this) {
DATE_ADDED, DATE_PUBLISHED, RANDOM -> "date"
POPULAR_TODAY, POPULAR_WEEK, POPULAR_MONTH, POPULAR_YEAR -> "popular"
}
val orderByKey: String
get() = when (this) {
DATE_ADDED, RANDOM -> "added"
DATE_PUBLISHED -> "published"
POPULAR_TODAY -> "today"
POPULAR_WEEK -> "week"
POPULAR_MONTH -> "month"
POPULAR_YEAR -> "year"
}
}
//searchlib.js //searchlib.js
const val separator = "-" const val separator = "-"
const val extension = ".html" const val extension = ".html"
@@ -39,51 +87,35 @@ val tag_index_version: String by lazy { getIndexVersion("tagindex") }
val galleries_index_version: String by lazy { getIndexVersion("galleriesindex") } val galleries_index_version: String by lazy { getIndexVersion("galleriesindex") }
val tagIndexDomain = "tagindex.hitomi.la" val tagIndexDomain = "tagindex.hitomi.la"
fun sha256(data: ByteArray) : ByteArray { fun sha256(data: ByteArray): ByteArray {
return MessageDigest.getInstance("SHA-256").digest(data) return MessageDigest.getInstance("SHA-256").digest(data)
} }
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun hashTerm(term: String) : UByteArray { fun hashTerm(term: String): UByteArray {
return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4) return sha256(term.toByteArray()).toUByteArray().sliceArray(0 until 4)
} }
fun sanitize(input: String) : String { fun sanitize(input: String): String {
return input.replace(Regex("[/#]"), "") return input.replace(Regex("[/#]"), "")
} }
fun getIndexVersion(name: String) = fun getIndexVersion(name: String) =
URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText() URL("$protocol//$domain/$name/version?_=${System.currentTimeMillis()}").readText()
//search.js //search.js
fun getGalleryIDsForQuery(query: String) : Set<Int> { fun getGalleryIDsForQuery(query: String, sortMode: SortMode): Set<Int> {
query.replace("_", " ").let { val sanitizedQuery = query.replace("_", " ")
if (it.indexOf(':') > -1) {
val sides = it.split(":")
val ns = sides[0]
var tag = sides[1]
var area : String? = ns val args = SearchArgs.fromQuery(sanitizedQuery)
var language = "all"
when (ns) {
"female", "male" -> {
area = "tag"
tag = it
}
"language" -> {
area = null
language = tag
tag = "index"
}
}
return getGalleryIDsFromNozomi(area, tag, language) return if (args != null) {
} getGalleryIDsFromNozomi(args, sortMode)
} else {
val key = hashTerm(it) val key = hashTerm(sanitizedQuery)
val field = "galleries" val field = "galleries"
val node = getNodeAtAddress(field, 0) ?: return emptySet() val node = getNodeAtAddress(field, 0)
val data = bSearch(field, key, node) val data = bSearch(field, key, node)
@@ -95,14 +127,14 @@ fun getGalleryIDsForQuery(query: String) : Set<Int> {
} }
fun encodeSearchQueryForUrl(s: Char) = fun encodeSearchQueryForUrl(s: Char) =
when(s) { when (s) {
' ' -> "_" ' ' -> "_"
'/' -> "slash" '/' -> "slash"
'.' -> "dot" '.' -> "dot"
else -> s.toString() else -> s.toString()
} }
fun getSuggestionsForQuery(query: String) : List<Suggestion> { fun getSuggestionsForQuery(query: String): List<Suggestion> {
query.replace('_', ' ').let { query.replace('_', ' ').let {
var field = "global" var field = "global"
var term = it var term = it
@@ -114,13 +146,16 @@ fun getSuggestionsForQuery(query: String) : List<Suggestion> {
} }
val chars = term.map(::encodeSearchQueryForUrl) val chars = term.map(::encodeSearchQueryForUrl)
val url = "https://$tagIndexDomain/$field${if (chars.isNotEmpty()) "/${chars.joinToString("/")}" else ""}.json" val url =
"https://$tagIndexDomain/$field${if (chars.isNotEmpty()) "/${chars.joinToString("/")}" else ""}.json"
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.build() .build()
val suggestions = json.parseToJsonElement(client.newCall(request).execute().body()?.use { body -> body.string() } ?: return emptyList()) val suggestions = json.parseToJsonElement(
client.newCall(request).execute().body()?.use { body -> body.string() }
?: return emptyList())
return buildList { return buildList {
suggestions.jsonArray.forEach { suggestionRaw -> suggestions.jsonArray.forEach { suggestionRaw ->
@@ -131,26 +166,34 @@ fun getSuggestionsForQuery(query: String) : List<Suggestion> {
val ns = suggestion[2].content ?: "" val ns = suggestion[2].content ?: ""
val tagname = sanitize(suggestion[0].content ?: return@forEach) val tagname = sanitize(suggestion[0].content ?: return@forEach)
val url = when(ns) { val url = when (ns) {
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension" "female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
"language" -> "/index-$tagname${separator}1$extension" "language" -> "/index-$tagname${separator}1$extension"
else -> "/$ns/$tagname${separator}all${separator}1$extension" else -> "/$ns/$tagname${separator}all${separator}1$extension"
} }
add(Suggestion(suggestion[0].content ?: "", suggestion[1].content?.toIntOrNull() ?: 0, url, ns)) add(
Suggestion(
suggestion[0].content ?: "",
suggestion[1].content?.toIntOrNull() ?: 0,
url,
ns
)
)
} }
} }
} }
} }
data class Suggestion(val s: String, val t: Int, val u: String, val n: String) data class Suggestion(val s: String, val t: Int, val u: String, val n: String)
fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggestion> {
fun getSuggestionsFromData(field: String, data: Pair<Long, Int>): List<Suggestion> {
val url = "$protocol//$domain/$index_dir/$field.$tag_index_version.data" val url = "$protocol//$domain/$index_dir/$field.$tag_index_version.data"
val (offset, length) = data val (offset, length) = data
if (length > 10000 || length <= 0) if (length > 10000 || length <= 0)
throw Exception("length $length is too long") throw Exception("length $length is too long")
val inbuf = getURLAtRange(url, offset.until(offset+length)) val inbuf = getURLAtRange(url, offset.until(offset + length))
val suggestions = ArrayList<Suggestion>() val suggestions = ArrayList<Suggestion>()
@@ -165,23 +208,25 @@ fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggesti
for (i in 0.until(numberOfSuggestions)) { for (i in 0.until(numberOfSuggestions)) {
var top = buffer.int var top = buffer.int
val ns = inbuf.sliceArray(buffer.position().until(buffer.position()+top)).toString(charset("UTF-8")) val ns = inbuf.sliceArray(buffer.position().until(buffer.position() + top))
buffer.position(buffer.position()+top) .toString(charset("UTF-8"))
buffer.position(buffer.position() + top)
top = buffer.int top = buffer.int
val tag = inbuf.sliceArray(buffer.position().until(buffer.position()+top)).toString(charset("UTF-8")) val tag = inbuf.sliceArray(buffer.position().until(buffer.position() + top))
buffer.position(buffer.position()+top) .toString(charset("UTF-8"))
buffer.position(buffer.position() + top)
val count = buffer.int val count = buffer.int
val tagname = sanitize(tag) val tagname = sanitize(tag)
val u = val u =
when(ns) { when (ns) {
"female", "male" -> "/tag/$ns:$tagname${separator}1$extension" "female", "male" -> "/tag/$ns:$tagname${separator}1$extension"
"language" -> "/index-$tagname${separator}1$extension" "language" -> "/index-$tagname${separator}1$extension"
else -> "/$ns/$tagname${separator}all${separator}1$extension" else -> "/$ns/$tagname${separator}all${separator}1$extension"
} }
suggestions.add(Suggestion(tag, count, u, ns)) suggestions.add(Suggestion(tag, count, u, ns))
} }
@@ -189,12 +234,17 @@ fun getSuggestionsFromData(field: String, data: Pair<Long, Int>) : List<Suggesti
return suggestions return suggestions
} }
fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<Int> { fun nozomiAddressFromArgs(args: SearchArgs, sortMode: SortMode) = when {
val nozomiAddress = sortMode != SortMode.DATE_ADDED && sortMode != SortMode.RANDOM ->
when(area) { if (args.area == "all") "$protocol//$domain/$compressed_nozomi_prefix/${sortMode.orderBy}/${sortMode.orderByKey}-${args.language}$nozomiextension"
null -> "$protocol//$domain/$compressed_nozomi_prefix/$tag-$language$nozomiextension" else "$protocol//$domain/$compressed_nozomi_prefix/${args.area}/${sortMode.orderBy}/${sortMode.orderByKey}/${args.tag}-${args.language}$nozomiextension"
else -> "$protocol//$domain/$compressed_nozomi_prefix/$area/$tag-$language$nozomiextension"
} args.area == "all" -> "$protocol//$domain/$compressed_nozomi_prefix/${args.tag}-${args.language}$nozomiextension"
else -> "$protocol//$domain/$compressed_nozomi_prefix/${args.area}/${args.tag}-${args.language}$nozomiextension"
}
fun getGalleryIDsFromNozomi(args: SearchArgs, sortMode: SortMode): Set<Int> {
val nozomiAddress = nozomiAddressFromArgs(args, sortMode)
val bytes = URL(nozomiAddress).readBytes() val bytes = URL(nozomiAddress).readBytes()
@@ -210,13 +260,13 @@ fun getGalleryIDsFromNozomi(area: String?, tag: String, language: String) : Set<
return nozomi return nozomi
} }
fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> { fun getGalleryIDsFromData(data: Pair<Long, Int>): Set<Int> {
val url = "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.data" val url = "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.data"
val (offset, length) = data val (offset, length) = data
if (length > 100000000 || length <= 0) if (length > 100000000 || length <= 0)
throw Exception("length $length is too long") throw Exception("length $length is too long")
val inbuf = getURLAtRange(url, offset.until(offset+length)) val inbuf = getURLAtRange(url, offset.until(offset + length))
val galleryIDs = mutableSetOf<Int>() val galleryIDs = mutableSetOf<Int>()
@@ -226,7 +276,7 @@ fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> {
val numberOfGalleryIDs = buffer.int val numberOfGalleryIDs = buffer.int
val expectedLength = numberOfGalleryIDs*4+4 val expectedLength = numberOfGalleryIDs * 4 + 4
if (numberOfGalleryIDs > 10000000 || numberOfGalleryIDs <= 0) if (numberOfGalleryIDs > 10000000 || numberOfGalleryIDs <= 0)
throw Exception("number_of_galleryids $numberOfGalleryIDs is too long") throw Exception("number_of_galleryids $numberOfGalleryIDs is too long")
@@ -239,33 +289,38 @@ fun getGalleryIDsFromData(data: Pair<Long, Int>) : Set<Int> {
return galleryIDs return galleryIDs
} }
fun getNodeAtAddress(field: String, address: Long) : Node? { fun getNodeAtAddress(field: String, address: Long): Node {
val url = val url =
when(field) { when (field) {
"galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.index" "galleries" -> "$protocol//$domain/$galleries_index_dir/galleries.$galleries_index_version.index"
"languages" -> "$protocol//$domain/$galleries_index_dir/languages.$galleries_index_version.index" "languages" -> "$protocol//$domain/$galleries_index_dir/languages.$galleries_index_version.index"
"nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.$galleries_index_version.index" "nozomiurl" -> "$protocol//$domain/$galleries_index_dir/nozomiurl.$galleries_index_version.index"
else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index" else -> "$protocol//$domain/$index_dir/$field.$tag_index_version.index"
} }
val nodedata = getURLAtRange(url, address.until(address+ max_node_size)) val nodedata = getURLAtRange(url, address.until(address + max_node_size))
return decodeNode(nodedata) return decodeNode(nodedata)
} }
fun getURLAtRange(url: String, range: LongRange) : ByteArray { fun getURLAtRange(url: String, range: LongRange): ByteArray {
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
.header("Range", "bytes=${range.first}-${range.last}") .header("Range", "bytes=${range.first}-${range.last}")
.build() .build()
return client.newCall(request).execute().body()?.use { it.bytes() } ?: byteArrayOf() return client.newCall(request).execute().body()?.use { it.bytes() } ?: byteArrayOf()
} }
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
data class Node(val keys: List<UByteArray>, val datas: List<Pair<Long, Int>>, val subNodeAddresses: List<Long>) data class Node(
val keys: List<UByteArray>,
val datas: List<Pair<Long, Int>>,
val subNodeAddresses: List<Long>
)
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun decodeNode(data: ByteArray) : Node { fun decodeNode(data: ByteArray): Node {
val buffer = ByteBuffer val buffer = ByteBuffer
.wrap(data) .wrap(data)
.order(ByteOrder.BIG_ENDIAN) .order(ByteOrder.BIG_ENDIAN)
@@ -281,8 +336,8 @@ fun decodeNode(data: ByteArray) : Node {
if (keySize == 0 || keySize > 32) if (keySize == 0 || keySize > 32)
throw Exception("fatal: !keySize || keySize > 32") throw Exception("fatal: !keySize || keySize > 32")
keys.add(uData.sliceArray(buffer.position().until(buffer.position()+keySize))) keys.add(uData.sliceArray(buffer.position().until(buffer.position() + keySize)))
buffer.position(buffer.position()+keySize) buffer.position(buffer.position() + keySize)
} }
val numberOfDatas = buffer.int val numberOfDatas = buffer.int
@@ -295,7 +350,7 @@ fun decodeNode(data: ByteArray) : Node {
datas.add(Pair(offset, length)) datas.add(Pair(offset, length))
} }
val numberOfSubNodeAddresses = B +1 val numberOfSubNodeAddresses = B + 1
val subNodeAddresses = ArrayList<Long>() val subNodeAddresses = ArrayList<Long>()
for (i in 0.until(numberOfSubNodeAddresses)) { for (i in 0.until(numberOfSubNodeAddresses)) {
@@ -307,8 +362,8 @@ fun decodeNode(data: ByteArray) : Node {
} }
@OptIn(ExperimentalUnsignedTypes::class) @OptIn(ExperimentalUnsignedTypes::class)
fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? { fun bSearch(field: String, key: UByteArray, node: Node): Pair<Long, Int>? {
fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray) : Int { fun compareArrayBuffers(dv1: UByteArray, dv2: UByteArray): Int {
val top = min(dv1.size, dv2.size) val top = min(dv1.size, dv2.size)
for (i in 0.until(top)) { for (i in 0.until(top)) {
@@ -321,18 +376,18 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
return 0 return 0
} }
fun locateKey(key: UByteArray, node: Node) : Pair<Boolean, Int> { fun locateKey(key: UByteArray, node: Node): Pair<Boolean, Int> {
for (i in node.keys.indices) { for (i in node.keys.indices) {
val cmpResult = compareArrayBuffers(key, node.keys[i]) val cmpResult = compareArrayBuffers(key, node.keys[i])
if (cmpResult <= 0) if (cmpResult <= 0)
return Pair(cmpResult==0, i) return Pair(cmpResult == 0, i)
} }
return Pair(false, node.keys.size) return Pair(false, node.keys.size)
} }
fun isLeaf(node: Node) : Boolean { fun isLeaf(node: Node): Boolean {
for (subnode in node.subNodeAddresses) for (subnode in node.subNodeAddresses)
if (subnode != 0L) if (subnode != 0L)
return false return false
@@ -349,6 +404,6 @@ fun bSearch(field: String, key: UByteArray, node: Node) : Pair<Long, Int>? {
else if (isLeaf(node)) else if (isLeaf(node))
return null return null
val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where]) ?: return null val nextNode = getNodeAtAddress(field, node.subNodeAddresses[where])
return bSearch(field, key, nextNode) return bSearch(field, key, nextNode)
} }

View File

@@ -33,7 +33,6 @@ import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
@@ -42,19 +41,40 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.navigation.NavigationView import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics import com.google.firebase.crashlytics.FirebaseCrashlytics
import kotlinx.coroutines.* import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeout
import kotlinx.coroutines.withTimeoutOrNull
import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.FloatingSearchView
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.floatingsearchview.util.view.MenuView import xyz.quaver.floatingsearchview.util.view.MenuView
import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.floatingsearchview.util.view.SearchInputView
import xyz.quaver.pupil.* import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.databinding.MainActivityBinding import xyz.quaver.pupil.databinding.MainActivityBinding
import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.histories
import xyz.quaver.pupil.hitomi.SortMode
import xyz.quaver.pupil.hitomi.doSearch import xyz.quaver.pupil.hitomi.doSearch
import xyz.quaver.pupil.hitomi.getGalleryIDsFromNozomi
import xyz.quaver.pupil.hitomi.getSuggestionsForQuery import xyz.quaver.pupil.hitomi.getSuggestionsForQuery
import xyz.quaver.pupil.searchHistory
import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.types.* import xyz.quaver.pupil.types.FavoriteHistorySwitch
import xyz.quaver.pupil.types.LoadingSuggestion
import xyz.quaver.pupil.types.NoResultSuggestion
import xyz.quaver.pupil.types.Suggestion
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.ui.dialog.GalleryDialog
import xyz.quaver.pupil.ui.view.MainView import xyz.quaver.pupil.ui.view.MainView
@@ -73,10 +93,19 @@ import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
val sortModeLookup = mapOf(
R.id.main_menu_sort_date_added to SortMode.DATE_ADDED,
R.id.main_menu_sort_date_published to SortMode.DATE_PUBLISHED,
R.id.main_menu_sort_popular_today to SortMode.POPULAR_TODAY,
R.id.main_menu_sort_popular_week to SortMode.POPULAR_WEEK,
R.id.main_menu_sort_popular_month to SortMode.POPULAR_MONTH,
R.id.main_menu_sort_popular_year to SortMode.POPULAR_YEAR,
R.id.main_menu_sort_random to SortMode.RANDOM
)
class MainActivity : class MainActivity :
BaseActivity(), BaseActivity(),
NavigationView.OnNavigationItemSelectedListener NavigationView.OnNavigationItemSelectedListener {
{
enum class Mode { enum class Mode {
SEARCH, SEARCH,
@@ -85,25 +114,21 @@ class MainActivity :
FAVORITE FAVORITE
} }
enum class SortMode {
NEWEST,
POPULAR
}
private val galleries = ArrayList<Int>() private val galleries = ArrayList<Int>()
private var query = "" private var query = ""
set(value) { set(value) {
field = value field = value
with(findViewById<SearchInputView>(R.id.search_bar_text)) { with(findViewById<SearchInputView>(R.id.search_bar_text)) {
if (text.toString() != value) if (text.toString() != value)
setText(query, TextView.BufferType.EDITABLE) setText(query, TextView.BufferType.EDITABLE)
}
} }
}
private var queryStack = mutableListOf<String>() private var queryStack = mutableListOf<String>()
private var mode = Mode.SEARCH private var mode = Mode.SEARCH
private var sortMode = SortMode.NEWEST private var sortMode = SortMode.DATE_ADDED
private var galleryIDs: Deferred<List<Int>>? = null private var galleryIDs: Deferred<List<Int>>? = null
private var totalItems = 0 private var totalItems = 0
@@ -112,11 +137,12 @@ class MainActivity :
private lateinit var binding: MainActivityBinding private lateinit var binding: MainActivityBinding
private val requestNotificationPermssionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> private val requestNotificationPermssionLauncher =
if (!isGranted) { registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
showNotificationPermissionExplanationDialog(this) if (!isGranted) {
showNotificationPermissionExplanationDialog(this)
}
} }
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -127,9 +153,17 @@ class MainActivity :
intent.dataString?.let { url -> intent.dataString?.let { url ->
restore(url, restore(url,
onFailure = { onFailure = {
Snackbar.make(binding.contents.recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show() Snackbar.make(
binding.contents.recyclerview,
R.string.settings_backup_failed,
Snackbar.LENGTH_LONG
).show()
}, onSuccess = { }, onSuccess = {
Snackbar.make(binding.contents.recyclerview, getString(R.string.settings_restore_success, it), Snackbar.LENGTH_LONG).show() Snackbar.make(
binding.contents.recyclerview,
getString(R.string.settings_restore_success, it),
Snackbar.LENGTH_LONG
).show()
} }
) )
} }
@@ -138,17 +172,24 @@ class MainActivity :
requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {} requestNotificationPermission(this, requestNotificationPermssionLauncher, false) {}
if (Preferences["download_folder", ""].isEmpty()) if (Preferences["download_folder", ""].isEmpty())
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog") DownloadLocationDialogFragment().show(
supportFragmentManager,
"Download Location Dialog"
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Preferences["download_folder_ignore_warning", false] && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !Preferences["download_folder_ignore_warning", false] &&
ContextCompat.getExternalFilesDirs(this, null).filterNotNull().map { Uri.fromFile(it).toString() } ContextCompat.getExternalFilesDirs(this, null).filterNotNull()
.map { Uri.fromFile(it).toString() }
.contains(Preferences["download_folder", ""]) .contains(Preferences["download_folder", ""])
) { ) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage(R.string.unaccessible_download_folder) .setMessage(R.string.unaccessible_download_folder)
.setPositiveButton(android.R.string.ok) { _, _ -> .setPositiveButton(android.R.string.ok) { _, _ ->
DownloadLocationDialogFragment().show(supportFragmentManager, "Download Location Dialog") DownloadLocationDialogFragment().show(
supportFragmentManager,
"Download Location Dialog"
)
}.setNegativeButton(R.string.ignore) { _, _ -> }.setNegativeButton(R.string.ignore) { _, _ ->
Preferences["download_folder_ignore_warning"] = true Preferences["download_folder_ignore_warning"] = true
}.show() }.show()
@@ -163,10 +204,12 @@ class MainActivity :
checkUpdate(this) checkUpdate(this)
} }
@OptIn(ExperimentalStdlibApi::class)
override fun onBackPressed() { override fun onBackPressed() {
when { when {
binding.drawer.isDrawerOpen(GravityCompat.START) -> binding.drawer.closeDrawer(GravityCompat.START) binding.drawer.isDrawerOpen(GravityCompat.START) -> binding.drawer.closeDrawer(
GravityCompat.START
)
queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread { queryStack.removeLastOrNull() != null && queryStack.isNotEmpty() -> runOnUiThread {
query = queryStack.last() query = queryStack.last()
@@ -175,6 +218,7 @@ class MainActivity :
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
else -> super.onBackPressed() else -> super.onBackPressed()
} }
} }
@@ -189,7 +233,7 @@ class MainActivity :
val perPage = Preferences["per_page", "25"].toInt() val perPage = Preferences["per_page", "25"].toInt()
val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt() val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt()
return when(keyCode) { return when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> { KeyEvent.KEYCODE_VOLUME_UP -> {
if (currentPage > 0) { if (currentPage > 0) {
runOnUiThread { runOnUiThread {
@@ -204,6 +248,7 @@ class MainActivity :
true true
} }
KeyEvent.KEYCODE_VOLUME_DOWN -> { KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (currentPage < maxPage) { if (currentPage < maxPage) {
runOnUiThread { runOnUiThread {
@@ -218,20 +263,22 @@ class MainActivity :
true true
} }
else -> super.onKeyDown(keyCode, event) else -> super.onKeyDown(keyCode, event)
} }
} }
private fun initView() { private fun initView() {
binding.contents.recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener() { binding.contents.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
// -height of the search view < translationY < 0 // -height of the search view < translationY < 0
binding.contents.searchview.translationY = binding.contents.searchview.translationY =
min( min(
max( max(
binding.contents.searchview.translationY - dy, binding.contents.searchview.translationY - dy,
-binding.contents.searchview.binding.querySection.root.height.toFloat() -binding.contents.searchview.binding.querySection.root.height.toFloat()
), 0F) ), 0F
)
if (dy > 0) if (dy > 0)
binding.contents.fab.hideMenuButton(true) binding.contents.fab.hideMenuButton(true)
@@ -240,7 +287,12 @@ class MainActivity :
} }
}) })
Linkify.addLinks(binding.contents.noresult, Pattern.compile(getString(R.string.https_text)), null, null, { _, _ -> getString(R.string.https) }) Linkify.addLinks(
binding.contents.noresult,
Pattern.compile(getString(R.string.https_text)),
null,
null,
{ _, _ -> getString(R.string.https) })
//NavigationView //NavigationView
binding.navView.setNavigationItemSelectedListener(this) binding.navView.setNavigationItemSelectedListener(this)
@@ -261,14 +313,17 @@ class MainActivity :
AlertDialog.Builder(context).apply { AlertDialog.Builder(context).apply {
setView(editText) setView(editText)
setTitle(R.string.main_jump_title) setTitle(R.string.main_jump_title)
setMessage(getString( setMessage(
R.string.main_jump_message, getString(
currentPage+1, R.string.main_jump_message,
ceil(totalItems / perPage.toDouble()).roundToInt() currentPage + 1,
)) ceil(totalItems / perPage.toDouble()).roundToInt()
)
)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1 currentPage =
(editText.text.toString().toIntOrNull() ?: return@setPositiveButton) - 1
runOnUiThread { runOnUiThread {
cancelFetch() cancelFetch()
@@ -322,7 +377,8 @@ class MainActivity :
setTitle(R.string.main_open_gallery_by_id) setTitle(R.string.main_open_gallery_by_id)
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
val galleryID = editText.text.toString().toIntOrNull() ?: return@setPositiveButton val galleryID =
editText.text.toString().toIntOrNull() ?: return@setPositiveButton
GalleryDialog(this@MainActivity, galleryID).apply { GalleryDialog(this@MainActivity, galleryID).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
@@ -344,7 +400,7 @@ class MainActivity :
} }
with(binding.contents.view) { with(binding.contents.view) {
setOnPageTurnListener(object: MainView.OnPageTurnListener { setOnPageTurnListener(object : MainView.OnPageTurnListener {
override fun onPrev(page: Int) { override fun onPrev(page: Int) {
currentPage-- currentPage--
@@ -409,10 +465,11 @@ class MainActivity :
this@MainActivity, this@MainActivity,
requestNotificationPermssionLauncher requestNotificationPermssionLauncher
) { ) {
if (DownloadManager.getInstance(context).isDownloading(galleryID)) { //download in progress if (DownloadManager.getInstance(context)
.isDownloading(galleryID)
) { //download in progress
DownloadService.cancel(this@MainActivity, galleryID) DownloadService.cancel(this@MainActivity, galleryID)
} } else {
else {
DownloadManager.getInstance(context).addDownloadFolder(galleryID) DownloadManager.getInstance(context).addDownloadFolder(galleryID)
DownloadService.download(this@MainActivity, galleryID) DownloadService.download(this@MainActivity, galleryID)
} }
@@ -485,6 +542,7 @@ class MainActivity :
TagSuggestion(it.tag, -1, "", it.area ?: "tag") TagSuggestion(it.tag, -1, "", it.area ?: "tag")
} + FavoriteHistorySwitch(getString(R.string.search_show_histories)) } + FavoriteHistorySwitch(getString(R.string.search_show_histories))
} }
else -> { else -> {
searchHistory.map { searchHistory.map {
Suggestion(it) Suggestion(it)
@@ -492,7 +550,7 @@ class MainActivity :
} }
}.reversed() }.reversed()
private var suggestionJob : Job? = null private var suggestionJob: Job? = null
private fun setupSearchBar() { private fun setupSearchBar() {
with(binding.contents.searchview) { with(binding.contents.searchview) {
val scrollSuggestionToTop = { val scrollSuggestionToTop = {
@@ -500,7 +558,10 @@ class MainActivity :
MainScope().launch { MainScope().launch {
withTimeout(1000) { withTimeout(1000) {
val layoutManager = layoutManager as LinearLayoutManager val layoutManager = layoutManager as LinearLayoutManager
while (layoutManager.findLastVisibleItemPosition() != adapter?.itemCount?.minus(1)) { while (layoutManager.findLastVisibleItemPosition() != adapter?.itemCount?.minus(
1
)
) {
layoutManager.scrollToPosition(adapter?.itemCount?.minus(1) ?: 0) layoutManager.scrollToPosition(adapter?.itemCount?.minus(1) ?: 0)
delay(100) delay(100)
} }
@@ -509,7 +570,7 @@ class MainActivity :
} }
} }
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener { onMenuStatusChangeListener = object : FloatingSearchView.OnMenuStatusChangeListener {
override fun onMenuOpened() { override fun onMenuOpened() {
(this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems() (this@MainActivity.binding.contents.recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
} }
@@ -561,7 +622,8 @@ class MainActivity :
suggestionJob = CoroutineScope(Dispatchers.IO).launch { suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = kotlin.runCatching { val suggestions = kotlin.runCatching {
getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }.toMutableList() getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }
.toMutableList()
}.getOrElse { mutableListOf() } }.getOrElse { mutableListOf() }
suggestions.filter { suggestions.filter {
@@ -573,12 +635,16 @@ class MainActivity :
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString()))) swapSuggestions(
if (suggestions.isNotEmpty()) suggestions else listOf(
NoResultSuggestion(getText(R.string.main_no_result).toString())
)
)
} }
} }
} }
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener { onFocusChangeListener = object : FloatingSearchView.OnFocusChangeListener {
override fun onFocus() { override fun onFocus() {
if (query.isEmpty() or query.endsWith(' ')) { if (query.isEmpty() or query.endsWith(' ')) {
swapSuggestions(defaultSuggestions) swapSuggestions(defaultSuggestions)
@@ -604,8 +670,14 @@ class MainActivity :
} }
fun onActionMenuItemSelected(item: MenuItem?) { fun onActionMenuItemSelected(item: MenuItem?) {
when(item?.itemId) { when (item?.itemId) {
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) R.id.main_menu_settings -> startActivity(
Intent(
this@MainActivity,
SettingsActivity::class.java
)
)
R.id.main_menu_thin -> { R.id.main_menu_thin -> {
val thin = !item.isChecked val thin = !item.isChecked
@@ -620,21 +692,15 @@ class MainActivity :
adapter = adapter // Force to redraw adapter = adapter // Force to redraw
} }
} }
R.id.main_menu_sort_newest -> {
sortMode = SortMode.NEWEST
item.isChecked = true
runOnUiThread { R.id.main_menu_sort_date_added,
currentPage = 0 R.id.main_menu_sort_date_published,
R.id.main_menu_sort_popular_today,
cancelFetch() R.id.main_menu_sort_popular_week,
clearGalleries() R.id.main_menu_sort_popular_month,
fetchGalleries(query, sortMode) R.id.main_menu_sort_popular_year,
loadBlocks() R.id.main_menu_sort_random -> {
} sortMode = sortModeLookup[item.itemId]!!
}
R.id.main_menu_sort_popular -> {
sortMode = SortMode.POPULAR
item.isChecked = true item.isChecked = true
runOnUiThread { runOnUiThread {
@@ -653,7 +719,7 @@ class MainActivity :
runOnUiThread { runOnUiThread {
binding.drawer.closeDrawers() binding.drawer.closeDrawers()
when(item.itemId) { when (item.itemId) {
R.id.main_drawer_home -> { R.id.main_drawer_home -> {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -664,6 +730,7 @@ class MainActivity :
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_history -> { R.id.main_drawer_history -> {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -674,6 +741,7 @@ class MainActivity :
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_downloads -> { R.id.main_drawer_downloads -> {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -684,6 +752,7 @@ class MainActivity :
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_favorite -> { R.id.main_drawer_favorite -> {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
@@ -694,20 +763,35 @@ class MainActivity :
fetchGalleries(query, sortMode) fetchGalleries(query, sortMode)
loadBlocks() loadBlocks()
} }
R.id.main_drawer_help -> { R.id.main_drawer_help -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
} }
R.id.main_drawer_github -> { R.id.main_drawer_github -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
} }
R.id.main_drawer_homepage -> { R.id.main_drawer_homepage -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page)))) startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(getString(R.string.home_page))
)
)
} }
R.id.main_drawer_email -> { R.id.main_drawer_email -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
} }
R.id.main_drawer_kakaotalk -> { R.id.main_drawer_kakaotalk -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord)))) startActivity(
Intent(
Intent.ACTION_VIEW,
Uri.parse(getString(R.string.discord))
)
)
} }
} }
} }
@@ -745,17 +829,18 @@ class MainActivity :
} }
if (query.isNotEmpty() && mode != Mode.SEARCH) { if (query.isNotEmpty() && mode != Mode.SEARCH) {
Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply { Snackbar.make(binding.contents.recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT)
setAction(android.R.string.ok) { .apply {
cancelFetch() setAction(android.R.string.ok) {
clearGalleries() cancelFetch()
currentPage = 0 clearGalleries()
mode = Mode.SEARCH currentPage = 0
queryStack.clear() mode = Mode.SEARCH
fetchGalleries(query, sortMode) queryStack.clear()
loadBlocks() fetchGalleries(query, sortMode)
} loadBlocks()
}.show() }
}.show()
} }
galleryIDs = null galleryIDs = null
@@ -764,22 +849,16 @@ class MainActivity :
return return
galleryIDs = CoroutineScope(Dispatchers.IO).async { galleryIDs = CoroutineScope(Dispatchers.IO).async {
when(mode) { when (mode) {
Mode.SEARCH -> { Mode.SEARCH -> {
when { doSearch(
query.isEmpty() and defaultQuery.isEmpty() -> { "$defaultQuery $query",
when(sortMode) { sortMode
SortMode.POPULAR -> getGalleryIDsFromNozomi(null, "popular", "all") ).also {
else -> getGalleryIDsFromNozomi(null, "index", "all") totalItems = it.size
}.also {
totalItems = it.size
}
}
else -> doSearch("$defaultQuery $query", sortMode == SortMode.POPULAR).also {
totalItems = it.size
}
} }
} }
Mode.HISTORY -> { Mode.HISTORY -> {
when { when {
query.isEmpty() -> { query.isEmpty() -> {
@@ -787,36 +866,42 @@ class MainActivity :
totalItems = it.size totalItems = it.size
} }
} }
else -> { else -> {
val result = doSearch(query).sorted() val result = doSearch(query, SortMode.DATE_ADDED).sorted()
histories.reversed().filter { result.binarySearch(it) >= 0 }.also { histories.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size totalItems = it.size
} }
} }
} }
} }
Mode.DOWNLOAD -> { Mode.DOWNLOAD -> {
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList() val downloads =
DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
when { when {
query.isEmpty() -> downloads.reversed().also { query.isEmpty() -> downloads.reversed().also {
totalItems = it.size totalItems = it.size
} }
else -> { else -> {
val result = doSearch(query).sorted() val result = doSearch(query, SortMode.DATE_ADDED).sorted()
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also { downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size totalItems = it.size
} }
} }
} }
} }
Mode.FAVORITE -> { Mode.FAVORITE -> {
when { when {
query.isEmpty() -> favorites.reversed().also { query.isEmpty() -> favorites.reversed().also {
totalItems = it.size totalItems = it.size
} }
else -> { else -> {
val result = doSearch(query).sorted() val result = doSearch(query, SortMode.DATE_ADDED).sorted()
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also { favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
totalItems = it.size totalItems = it.size
} }
@@ -849,10 +934,18 @@ class MainActivity :
} }
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
binding.contents.view.setCurrentPage(currentPage + 1, galleryIDs.size > (currentPage+1)*perPage) binding.contents.view.setCurrentPage(
currentPage + 1,
galleryIDs.size > (currentPage + 1) * perPage
)
} }
galleryIDs.slice(currentPage*perPage until min(currentPage*perPage+perPage, galleryIDs.size)).chunked(5).let { chunks -> galleryIDs.slice(
currentPage * perPage until min(
currentPage * perPage + perPage,
galleryIDs.size
)
).chunked(5).let { chunks ->
for (chunk in chunks) for (chunk in chunks)
chunk.map { galleryID -> chunk.map { galleryID ->
async { async {

View File

@@ -122,7 +122,7 @@ class DefaultQueryDialog : DialogFragment() {
s.replace( s.replace(
0, 0,
s.length, s.length,
s.toString().toLowerCase(java.util.Locale.getDefault()) s.toString().lowercase()
) )
} }
}) })

View File

@@ -21,7 +21,6 @@ package xyz.quaver.pupil.ui.dialog
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@@ -29,8 +28,6 @@ import androidx.core.content.ContextCompat
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import net.rdrei.android.dirchooser.DirectoryChooserActivity
import net.rdrei.android.dirchooser.DirectoryChooserConfig
import xyz.quaver.io.FileX import xyz.quaver.io.FileX
import xyz.quaver.io.util.toFile import xyz.quaver.io.util.toFile
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
@@ -56,8 +53,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
it.data?.data?.also { uri -> it.data?.data?.also { uri ->
val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION val takeFlags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) context.contentResolver.takePersistableUriPermission(uri, takeFlags)
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false)) { if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false)) {
entries[null]?.locationAvailable?.text = uri.toFile(context)?.canonicalPath entries[null]?.locationAvailable?.text = uri.toFile(context)?.canonicalPath
@@ -87,32 +83,6 @@ class DownloadLocationDialogFragment : DialogFragment() {
} }
} }
private val requestDownloadFolderOldLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
val context = context ?: return@registerForActivityResult
val dialog = dialog ?: return@registerForActivityResult
if (it.resultCode == DirectoryChooserActivity.RESULT_CODE_DIR_SELECTED) {
val directory = it.data?.getStringExtra(DirectoryChooserActivity.RESULT_SELECTED_DIR)!!
if (!File(directory).canWrite()) {
Snackbar.make(
dialog.window!!.decorView.rootView,
R.string.settings_download_folder_not_writable,
Snackbar.LENGTH_LONG
).show()
val downloadFolder = DownloadManager.getInstance(context).downloadFolder.canonicalPath
val key = entries.keys.firstOrNull { it?.canonicalPath == downloadFolder }
entries[key]!!.button.isChecked = true
if (key == null) entries[key]!!.locationAvailable.text = downloadFolder
}
else {
entries[null]?.locationAvailable?.text = directory
Preferences["download_folder"] = File(directory).toURI().toString()
}
}
}
private fun initView() { private fun initView() {
val externalFilesDirs = ContextCompat.getExternalFilesDirs(requireContext(), null) val externalFilesDirs = ContextCompat.getExternalFilesDirs(requireContext(), null)
@@ -147,24 +117,11 @@ class DownloadLocationDialogFragment : DialogFragment() {
} }
button.performClick() button.performClick()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply { putExtra("android.content.extra.SHOW_ADVANCED", true)
putExtra("android.content.extra.SHOW_ADVANCED", true)
}
requestDownloadFolderLauncher.launch(intent)
} else { // Can't use SAF on old Androids!
val config = DirectoryChooserConfig.builder()
.newDirectoryName("Pupil")
.allowNewDirectoryNameModification(true)
.build()
val intent = Intent(context, DirectoryChooserActivity::class.java).apply {
putExtra(DirectoryChooserActivity.EXTRA_CONFIG, config)
}
requestDownloadFolderOldLauncher.launch(intent)
} }
requestDownloadFolderLauncher.launch(intent)
} }
entries[null] = this entries[null] = this
} }

View File

@@ -76,7 +76,7 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
s ?: return s ?: return
if (s.any { it.isUpperCase() }) if (s.any { it.isUpperCase() })
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault())) s.replace(0, s.length, s.toString().lowercase())
} }
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) { override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Pupil, Hitomi.la viewer for Android ~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2020 tom5079 ~ Copyright (C) 2020 tom5079
~ ~
@@ -18,8 +17,8 @@
--> -->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.MainActivity"> tools:context=".ui.MainActivity">
@@ -32,44 +31,43 @@
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller <com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:addLastItemPadding="true"
app:handleDrawable="@drawable/thumb" app:handleDrawable="@drawable/thumb"
app:handleHasFixedSize="true" app:handleHasFixedSize="true"
app:handleHeight="72dp" app:handleHeight="72dp"
app:handleVisibilityDuration="1000"
app:handleWidth="24dp" app:handleWidth="24dp"
app:disableTrack="true" app:popupDrawable="@android:color/transparent"
app:hideHandleAfter="1000" app:trackMarginStart="64dp">
app:trackMarginStart="64dp"
app:addLastItemPadding="true"
app:popupDrawable="@android:color/transparent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview" android:id="@+id/recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:paddingTop="64dp"
android:clipToPadding="false" android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> android:paddingTop="64dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller> </com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
</xyz.quaver.pupil.ui.view.MainView> </xyz.quaver.pupil.ui.view.MainView>
<androidx.core.widget.ContentLoadingProgressBar <androidx.core.widget.ContentLoadingProgressBar
style="?android:attr/progressBarStyle"
android:id="@+id/progressbar" android:id="@+id/progressbar"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:indeterminate="true"/> android:indeterminate="true" />
<TextView <TextView
android:id="@+id/noresult" android:id="@+id/noresult"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:text="@string/main_no_result"
android:linksClickable="true" android:linksClickable="true"
android:visibility="invisible"/> android:text="@string/main_no_result"
android:visibility="invisible" />
<com.github.clans.fab.FloatingActionMenu <com.github.clans.fab.FloatingActionMenu
android:id="@+id/fab" android:id="@+id/fab"
@@ -84,28 +82,28 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_label="@string/main_fab_cancel" app:fab_label="@string/main_fab_cancel"
app:fab_size="mini"/> app:fab_size="mini" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/jump_fab" android:id="@+id/jump_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_label="@string/main_jump_title" app:fab_label="@string/main_jump_title"
app:fab_size="mini"/> app:fab_size="mini" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/random_fab" android:id="@+id/random_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_label="@string/main_fab_random" app:fab_label="@string/main_fab_random"
app:fab_size="mini"/> app:fab_size="mini" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/id_fab" android:id="@+id/id_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_label="@string/main_open_gallery_by_id" app:fab_label="@string/main_open_gallery_by_id"
app:fab_size="mini"/> app:fab_size="mini" />
</com.github.clans.fab.FloatingActionMenu> </com.github.clans.fab.FloatingActionMenu>
@@ -113,15 +111,15 @@
android:id="@+id/searchview" android:id="@+id/searchview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:close_search_on_keyboard_dismiss="false"
app:dismissOnOutsideTouch="true"
app:leftActionMode="showHamburger"
app:menu="@menu/main"
app:searchBarMarginLeft="6dp" app:searchBarMarginLeft="6dp"
app:searchBarMarginRight="6dp" app:searchBarMarginRight="6dp"
app:searchBarMarginTop="6dp" app:searchBarMarginTop="6dp"
app:searchHint="@string/search_hint" app:searchHint="@string/search_hint"
app:suggestionAnimDuration="250"
app:showSearchKey="true" app:showSearchKey="true"
app:leftActionMode="showHamburger" app:suggestionAnimDuration="250" />
app:menu="@menu/main"
app:dismissOnOutsideTouch="true"
app:close_search_on_keyboard_dismiss="false" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?><!--
<!--
~ Pupil, Hitomi.la viewer for Android ~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079 ~ Copyright (C) 2019 tom5079
~ ~
@@ -29,37 +28,37 @@
android:id="@+id/scroller" android:id="@+id/scroller"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:handleDrawable="@drawable/thumb"
app:handleHeight="72dp"
app:handleWidth="24dp"
app:disableTrack="true"
app:hideHandleAfter="1000"
app:handleHasFixedSize="true"
app:addLastItemPadding="true" app:addLastItemPadding="true"
app:handleDrawable="@drawable/thumb"
app:handleHasFixedSize="true"
app:handleHeight="72dp"
app:handleVisibilityDuration="1000"
app:handleWidth="24dp"
app:popupDrawable="@android:color/transparent"> app:popupDrawable="@android:color/transparent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerview" android:id="@+id/recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller> </com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
<include layout="@layout/reader_eye_card" <include
android:id="@+id/eye_card" android:id="@+id/eye_card"
android:visibility="gone" layout="@layout/reader_eye_card"
android:layout_height="wrap_content"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" android:layout_gravity="center_horizontal"
android:layout_margin="8dp"/> android:layout_margin="8dp"
android:visibility="gone" />
<ProgressBar <ProgressBar
android:id="@+id/download_progressbar" android:id="@+id/download_progressbar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal" style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="4dp"/> android:layout_height="4dp" />
<com.github.clans.fab.FloatingActionMenu <com.github.clans.fab.FloatingActionMenu
android:id="@+id/fab" android:id="@+id/fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -72,33 +71,33 @@
android:id="@+id/download_fab" android:id="@+id/download_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_download"
app:fab_label="@string/reader_fab_download" app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/> app:fab_size="mini"
app:srcCompat="@drawable/ic_download" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/retry_fab" android:id="@+id/retry_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/refresh"
app:fab_label="@string/reader_fab_retry" app:fab_label="@string/reader_fab_retry"
app:fab_size="mini"/> app:fab_size="mini"
app:srcCompat="@drawable/refresh" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/auto_fab" android:id="@+id/auto_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/eye_white"
app:fab_label="@string/reader_fab_auto" app:fab_label="@string/reader_fab_auto"
app:fab_size="mini"/> app:fab_size="mini"
app:srcCompat="@drawable/eye_white" />
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
android:id="@+id/fullscreen_fab" android:id="@+id/fullscreen_fab"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_fullscreen"
app:fab_label="@string/reader_fab_fullscreen" app:fab_label="@string/reader_fab_fullscreen"
app:fab_size="mini"/> app:fab_size="mini"
app:srcCompat="@drawable/ic_fullscreen" />
</com.github.clans.fab.FloatingActionMenu> </com.github.clans.fab.FloatingActionMenu>

View File

@@ -26,11 +26,21 @@
app:showAsAction="ifRoom"> app:showAsAction="ifRoom">
<menu> <menu>
<group android:checkableBehavior="single"> <group android:checkableBehavior="single">
<item android:id="@+id/main_menu_sort_newest" <item android:id="@+id/main_menu_sort_date_added"
android:title="@string/main_menu_sort_newest" android:title="@string/main_menu_sort_date_added"
android:checked="true"/> android:checked="true"/>
<item android:id="@+id/main_menu_sort_popular" <item android:id="@+id/main_menu_sort_date_published"
android:title="@string/main_menu_sort_popular"/> android:title="@string/main_menu_sort_date_published"/>
<item android:id="@+id/main_menu_sort_popular_today"
android:title="@string/main_menu_sort_popular_today"/>
<item android:id="@+id/main_menu_sort_popular_week"
android:title="@string/main_menu_sort_popular_week"/>
<item android:id="@+id/main_menu_sort_popular_month"
android:title="@string/main_menu_sort_popular_month"/>
<item android:id="@+id/main_menu_sort_popular_year"
android:title="@string/main_menu_sort_popular_year"/>
<item android:id="@+id/main_menu_sort_random"
android:title="@string/main_menu_sort_random"/>
</group> </group>
</menu> </menu>
</item> </item>

View File

@@ -1,165 +1,202 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="galleryblock_language">言語: %1$s</string>
<string name="galleryblock_series">シリーズ: %1$s</string>
<string name="galleryblock_type">タイプ: %1$s</string>
<string name="main_no_result">結果なし</string>
<string name="search_hint">ギャラリー検索</string>
<string name="settings_clear_cache">キャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_storage_usage">%s使用中</string>
<string name="settings_storage_usage_loading">ストレージ使用量読み込み中…</string>
<string name="settings_default_query">デフォルトキーワード</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string>
<string name="settings_title">設定</string>
<string name="update_notification_description">アップデートダウンロード中</string>
<string name="update_title">新しいアップデートがあります</string>
<string name="warning">注意</string> <string name="warning">注意</string>
<string name="settings_miscellaneous_title">その他</string> <string name="error">エラー</string>
<string name="settings_mirror_title">ミラーサーバー</string> <string name="ignore">無視</string>
<string name="settings_clear_history">履歴を削除</string> <string name="unlimited">制限なし</string>
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
<string name="settings_clear_history_summary">履歴数: %1$d</string> <string name="copied_to_clipboard">クリップボードにコピーしました</string>
<string name="main_drawer_history">履歴</string>
<string name="notification_denied">通知を無効にするとバックグラウンドダウンロード及びアプリのアップデート機能が使用不可になります。</string>
<string name="main_drawer_home">トップ</string>
<string name="update_release_note"># リリースノート(v%1$s)\n%2$s</string>
<string name="settings_security_mode_title">セキュリティーモード</string>
<string name="settings_security_mode_summary">アプリ履歴でアプリの画面を表示しない</string>
<string name="reader_go_to_page">移動</string>
<string name="default_query_dialog_language_selector_none">非選択</string>
<string name="default_query_dialog_filter_BL">BLフィルター</string>
<string name="default_query_dialog_filter_guro">グロフィルター</string>
<string name="default_query_dialog_language">"言語: "</string>
<string name="default_query_dialog_title">デフォルトキーワード設定</string>
<string name="main_drawer_group_contact_title">お問い合わせ先</string>
<string name="main_drawer_group_contact_homepage">ホームページ</string>
<string name="main_drawer_group_contact_help">ヘルプ</string>
<string name="main_drawer_group_contact_github">Github</string>
<string name="main_drawer_group_contact_email">メールを送る</string>
<string name="reader_fab_fullscreen">フルスクリーン</string>
<string name="channel_download">ダウンロード</string> <string name="channel_download">ダウンロード</string>
<string name="channel_download_description">ダウンロードの進行を通知</string> <string name="channel_download_description">ダウンロードの進行を通知</string>
<string name="reader_fab_download">バックグラウンドダウンロー</string> <string name="channel_downloader">ダウンロー</string>
<string name="reader_notification_text">ダウンロード中…</string> <string name="channel_downloader_description">ダウンローダの状態を表示</string>
<string name="reader_notification_complete">ダウンロード完了</string> <string name="channel_update">アップデート</string>
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</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">アンドロイド11以上では、現在のダウンロードフォルダに外部アプリからアクセスできません。ダウンロードフォルダを変更しますか</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_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">ディスコード</string>
<string name="main_menu_thin">簡単モード</string>
<string name="main_menu_sort">並び替え</string>
<string name="main_menu_sort_date_added">新しい順</string>
<string name="main_menu_sort_date_published">新しい順(発売日)</string>
<string name="main_menu_sort_popular_today">人気順(日間)</string>
<string name="main_menu_sort_popular_week">人気順(週間)</string>
<string name="main_menu_sort_popular_month">人気順(月間)</string>
<string name="main_menu_sort_popular_year">人気順(年間)</string>
<string name="main_menu_sort_random">ランダム</string>
<string name="main_jump_title">ページ移動</string> <string name="main_jump_title">ページ移動</string>
<string name="main_jump_message">現ページ番号: %1$d\nページ数: %2$d</string> <string name="main_jump_message">現ページ番号: %1$d\nページ数: %2$d</string>
<string name="channel_transfer">転送</string> <string name="main_open_gallery_by_id">IDで作品を開く</string>
<string name="unable_to_connect">hitomi.laに接続できません</string>
<string name="main_move_to_page">%1$dページへ移動</string>
<string name="settings_clear_downloads">ダウンロード削除</string>
<string name="settings_clear_downloads_alert_message">ダウンロードしたギャラリーを全て削除します。\n実行しますか</string>
<string name="settings_mirror_summary">ミラーサーバからイメージをロード</string>
<string name="main_drawer_favorite">ブックマーク</string>
<string name="main_open_gallery_by_id">ギャラリー番号で見る</string>
<string name="reader_failed_to_find_gallery">エラーが発生しました</string> <string name="reader_failed_to_find_gallery">エラーが発生しました</string>
<string name="settings_storage">ストレージ</string> <string name="main_fab_random">ランダムに作品を開く</string>
<string name="main_drawer_grouop_contact_discord">ディスコード</string> <string name="main_fab_cancel">すべてのダウンロードをキャンセル</string>
<string name="settings_app_lock">アプリロック</string>
<string name="settings_app_lock_type">アップロックの種類</string> <string name="main_move_to_page">%1$dページへ移動</string>
<string name="settings_app_version_title">バージョン(アップデート確認)</string>
<string name="settings_lock_biometrics">生体認識</string> <string name="main_download">ダウンロード</string>
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string> <string name="main_delete">削除</string>
<string name="settings_lock_enabled">有効</string>
<string name="settings_lock_fingerprint">指紋</string> <string name="update_title">最新版あり</string>
<string name="settings_lock_password">パスワード</string> <string name="update_download_completed">ダウンロードが完了しました</string>
<string name="settings_lock_pattern">パターン</string> <string name="update_download_completed_description">ここをクリックして更新</string>
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string> <string name="update_notification_description">最新版をダウンロード中&#8230;</string>
<string name="settings_lock_none">なし</string> <string name="update_release_note"># 更新履歴(v%1$s)\n%2$s</string>
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
<string name="reader_loading">ロード中</string> <string name="search_hint">作品を検索</string>
<string name="main_menu_sort">ソート</string> <string name="search_all">すべての作品を対象に検索</string>
<string name="main_menu_sort_newest">投稿日時順</string> <string name="search_show_histories">履歴を見る</string>
<string name="main_menu_sort_popular">人気順</string> <string name="search_show_tags">お気に入りのタグを見る</string>
<string name="ignore">無視</string>
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string> <string name="gallery_details">作品情報</string>
<string name="settings_dark_mode_title">ダークモード</string> <string name="gallery_thumbnails">サムネイル</string>
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string> <string name="gallery_related">おすすめ</string>
<string name="gallery_details">ギャラリー情報</string> <string name="gallery_artists">作者</string>
<string name="gallery_artists">アーティスト</string>
<string name="gallery_characters">キャラクター</string>
<string name="gallery_groups">グループ</string> <string name="gallery_groups">グループ</string>
<string name="gallery_language">言語</string> <string name="gallery_language">言語</string>
<string name="gallery_series">シリーズ</string> <string name="gallery_series">シリーズ</string>
<string name="gallery_characters">キャラクター</string>
<string name="gallery_tags">タグ</string> <string name="gallery_tags">タグ</string>
<string name="gallery_thumbnails">サムネイル</string>
<string name="gallery_related">おすすめ</string> <string name="galleryblock_series">シリーズ: %1$s</string>
<string name="settings_nomedia_title">イメージを隠す</string> <string name="galleryblock_type">タイプ: %1$s</string>
<string name="main_delete">削除</string> <string name="galleryblock_language">言語: %1$s</string>
<string name="main_download">ダウンロード</string>
<string name="settings_backup_title">ブックマークバックアップ</string> <!-- READER -->
<string name="settings_restore_title">ブックマーク復元</string> <string name="reader_loading">読込中</string>
<string name="settings_backup_file_created">バックアップファイルを作成しました</string> <string name="reader_go_to_page">移動</string>
<string name="settings_restore_failed">復元に失敗しました</string> <string name="reader_fab_fullscreen">全画面</string>
<string name="settings_restore_success">%1$d項目を復元しました</string> <string name="reader_fab_retry">再試行</string>
<string name="settings_download_folder">ダウンロード場所</string> <string name="reader_fab_auto">まばたき検知スクロール</string>
<string name="settings_download_folder_internal">内部ストレージ</string> <string name="reader_fab_auto_cancel">まばたき検知を中止</string>
<string name="settings_download_folder_removable">外部SDカード</string> <string name="reader_fab_download">バックグラウンドでダウンロード</string>
<string name="settings_download_folder_available">%s 使用可能</string> <string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string>
<string name="update_download_completed">ダウンロードが完了しました</string> <string name="reader_notification_text">ダウンロード中…</string>
<string name="update_download_completed_description">ここをクリックしてアップデートを行えます</string> <string name="reader_notification_complete">ダウンロード完了</string>
<string name="settings_beta">ベータチャンネルでアップデートを受信</string>
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
<string name="no_camera">この機器には前面カメラが装着されていません</string>
<string name="downloader_running">ダウンローダー起動中</string>
<string name="settings_title">設定</string>
<string name="settings_app_version_title">バージョン(クリックで更新確認)</string>
<string name="settings_app_version_description">v%s</string> <string name="settings_app_version_description">v%s</string>
<string name="settings_low_quality">低解像度イメージ</string> <string name="settings_beta">ベータ版チャンネルでアップデート</string>
<string name="settings_low_quality_summary">ロード速度とデータ使用料を改善するため低解像度イメージをロード</string>
<string name="settings_search_title">検索設定</string>
<string name="settings_galleries_per_page">一度に読み込む作品数</string>
<string name="settings_default_query">検索語句の初期値</string>
<string name="settings_storage">保存領域</string>
<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>
<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_custom">手動で設定</string>
<string name="settings_download_folder_not_writable">このフォルダにアクセスできません。他のフォルダを選択してください。</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>
<string name="settings_app_lock">アプリをロック</string>
<string name="settings_app_lock_type">アップをロックする方法</string>
<string name="settings_networking">ネットワーク</string>
<string name="settings_mirror_summary">ミラーサーバから画像を読み込む</string>
<string name="settings_proxy_title">プロクシ</string> <string name="settings_proxy_title">プロクシ</string>
<string name="settings_max_concurrent_download">並列ダウンロード</string>
<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>
<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>
<string name="settings_lock_confirm">ロック確認のためもう一回入力してください。</string>
<string name="settings_lock_enabled">有効</string>
<string name="settings_lock_none">なし</string>
<string name="settings_lock_pattern">パターン</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 指紋ロック™</string>
<string name="settings_lock_remove_message">ロックを無効にしますか?</string>
<string name="settings_lock_wrong_confirm">ロックが一致しません。やり直してください。</string>
<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">グロフィルター</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>
<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">ID</string> <string name="proxy_dialog_username_hint">ID</string>
<string name="proxy_dialog_type">プロクシタイプ</string>
<string name="proxy_dialog_port_hint">ポート</string>
<string name="proxy_dialog_password_hint">パスワード</string> <string name="proxy_dialog_password_hint">パスワード</string>
<string name="proxy_dialog_error">エラー</string> <string name="proxy_dialog_error">エラー</string>
<string name="proxy_dialog_addr_hint">サーバーアドレス</string>
<string name="proxy_dialog_server">サーバー</string> <string name="proxy_dialog_server">サーバー</string>
<string name="main_menu_thin">簡単モード</string>
<string name="main_fab_cancel">すべてのダウンロードキャンセル</string>
<string name="channel_update">アップデート</string>
<string name="channel_update_description">アップデートの進行状態を表示</string>
<string name="settings_import_old_galleries">旧ギャラリーインポート</string>
<string name="import_old_galleries_folder_not_readable">フォルダを読めません</string> <string name="import_old_galleries_folder_not_readable">フォルダを読めません</string>
<string name="import_old_galleries_notification">旧ギャラリーインポート中…</string> <string name="import_old_galleries_notification">旧ギャラリーインポート中…</string>
<string name="import_old_galleries_notification_done">インポート完了</string> <string name="import_old_galleries_notification_done">インポート完了</string>
<string name="main_fab_random">ランダムギャラリーを開く</string>
<string name="settings_lock_fingerprint_without_lock">予備のロックが設定されていないと指紋ロックは使用できません</string>
<string name="settings_lock_fingerprint_prompt">Pupil指紋ロック™</string>
<string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string> <string name="settings_lock_fingerprint_prompt_subtitle">こうかはばつぐんだ!</string>
<string name="default_query_dialog_filter_loli">登場人物を全て18歳以上にする</string>
<string name="settings_user_id">ユーザーID</string> </resources>
<string name="copied_to_clipboard">クリップボードにコピーしました</string>
<string name="reader_fab_retry">リトライ</string>
<string name="reader_fab_auto">まばたき検知スクロール</string>
<string name="search_all">全てのギャラリーを対象に検索</string>
<string name="settings_rtl">綴じ方向を左にする</string>
<string name="settings_manage_favorites">ブックマーク管理</string>
<string name="settings_backup_failed">エラーが発生しました</string>
<string name="settings_backup_share">バックアップ共有</string>
<string name="channel_downloader">ダウンローダ</string>
<string name="channel_downloader_description">ダウンローダの状態を表示</string>
<string name="downloader_running">ダウンローダー起動中</string>
<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_manage_storage">ストレージ管理</string>
<string name="settings_oss">オープンソースライセンス</string>
<string name="search_show_tags">お気に入りのタグを見る</string>
<string name="search_show_histories">履歴を見る</string>
<string name="reader_fab_auto_cancel">まばたき検知を中止</string>
<string name="camera_denied">カメラ権限が拒否されているため、まばたき検知使用できません</string>
<string name="no_camera">この機器には前面カメラが装着されていません</string>
<string name="error">エラー</string>
<string name="settings_cache_limit">キャッシュサイズ制限</string>
<string name="unlimited">制限なし</string>
<string name="settings_tag_translation">タグ言語</string>
<string name="settings_tag_translation_message">Githubにて翻訳に参加できます</string>
<string name="settings_max_concurrent_download">並列ダウンロード</string>
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか</string>
<string name="settings_networking">ネットワーク</string>
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
<string name="settings_transfer_data">他の機器にデータを転送</string>
<string name="channel_transfer_description">他の機器へのデータ転送の進捗度を表示</string>
</resources>

View File

@@ -45,6 +45,7 @@
<string name="reader_notification_complete">다운로드 완료</string> <string name="reader_notification_complete">다운로드 완료</string>
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string> <string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string>
<string name="main_drawer_downloads">다운로드</string> <string name="main_drawer_downloads">다운로드</string>
<string name="main_menu_sort_random">무작위</string>
<string name="main_jump_title">페이지 이동</string> <string name="main_jump_title">페이지 이동</string>
<string name="main_jump_message">현재 페이지: %1$d\n페이지 수: %2$d</string> <string name="main_jump_message">현재 페이지: %1$d\n페이지 수: %2$d</string>
<string name="channel_transfer">전송</string> <string name="channel_transfer">전송</string>
@@ -71,8 +72,6 @@
<string name="settings_lock_remove_message">잠금을 해제할까요?</string> <string name="settings_lock_remove_message">잠금을 해제할까요?</string>
<string name="reader_loading">로딩중</string> <string name="reader_loading">로딩중</string>
<string name="main_menu_sort">정렬</string> <string name="main_menu_sort">정렬</string>
<string name="main_menu_sort_popular">인기순</string>
<string name="main_menu_sort_newest">시간순</string>
<string name="ignore">무시</string> <string name="ignore">무시</string>
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string> <string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
<string name="settings_dark_mode_title">다크 모드</string> <string name="settings_dark_mode_title">다크 모드</string>
@@ -162,4 +161,10 @@
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string> <string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
<string name="settings_transfer_data">다른 기기에 데이터 전송</string> <string name="settings_transfer_data">다른 기기에 데이터 전송</string>
<string name="channel_transfer_description">다른 기기에 데이터 전송 시 상태 표시</string> <string name="channel_transfer_description">다른 기기에 데이터 전송 시 상태 표시</string>
<string name="main_menu_sort_date_added">추가일</string>
<string name="main_menu_sort_date_published">발매일</string>
<string name="main_menu_sort_popular_today">인기순 (오늘)</string>
<string name="main_menu_sort_popular_week">인기순 (이번 주)</string>
<string name="main_menu_sort_popular_month">인기순 (이번 달)</string>
<string name="main_menu_sort_popular_year">인기순 (이번 해)</string>
</resources> </resources>

View File

@@ -70,8 +70,13 @@
<string name="main_menu_thin">Thin Mode</string> <string name="main_menu_thin">Thin Mode</string>
<string name="main_menu_sort">Sort</string> <string name="main_menu_sort">Sort</string>
<string name="main_menu_sort_newest">Newest</string> <string name="main_menu_sort_date_added">Date Added</string>
<string name="main_menu_sort_popular">Popular</string> <string name="main_menu_sort_date_published">Date Published</string>
<string name="main_menu_sort_popular_today">Popular: Today</string>
<string name="main_menu_sort_popular_week">Popular: Week</string>
<string name="main_menu_sort_popular_month">Popular: Month</string>
<string name="main_menu_sort_popular_year">Popular: Year</string>
<string name="main_menu_sort_random">Random</string>
<string name="main_jump_title">Jump to page</string> <string name="main_jump_title">Jump to page</string>
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string> <string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>

View File

@@ -1,35 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"
classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version"
classpath "com.google.gms:google-services:4.3.15"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
classpath "com.google.firebase:firebase-crashlytics-gradle:2.9.9"
classpath "com.google.firebase:perf-plugin:1.4.2"
classpath "com.google.android.gms:oss-licenses-plugin:0.10.6"
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter()
maven { url "https://jitpack.io" }
maven { url "https://guardian.github.io/maven/repo-releases/" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}

11
build.gradle.kts Normal file
View File

@@ -0,0 +1,11 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.kotlin.compose) apply false
alias(libs.plugins.kotlinx.serialization) apply false
alias(libs.plugins.gms.oss.licenses) apply false
alias(libs.plugins.gms.google.services) apply false
alias(libs.plugins.firebase.crashlytics) apply false
alias(libs.plugins.firebase.perf) apply false
}

View File

@@ -20,4 +20,6 @@ kotlin.code.style=official
android.enableJetifier=true android.enableJetifier=true
android.useAndroidX=true android.useAndroidX=true
kotlin_version=1.9.0 kotlin_version=2.1.10
android.nonTransitiveRClass=false
android.nonFinalResIds=false

114
gradle/libs.versions.toml Normal file
View File

@@ -0,0 +1,114 @@
[versions]
agp = "8.8.1"
kotlin = "2.0.0"
activityKtx = "1.10.0"
appcompat = "1.7.0"
bigimageviewer = "1.8.1"
biometric = "1.1.0"
constraintlayout = "2.2.0"
core = "3.1.0"
coreKtx = "1.15.0"
documentfilex = "0.7.2"
espressoCore = "3.6.1"
fab = "1.6.4"
firebaseBom = "33.9.0"
firebaseCrashlyticsGradle = "3.0.3"
floatingsearchview = "1.1.7"
fragmentKtx = "1.8.5"
googleServices = "4.4.2"
gradle = "8.8.0"
gridlayout = "1.0.0"
imagepipelineOkhttp3 = "2.6.0"
jsoup = "1.18.3"
junit = "4.13.2"
junitVersion = "1.2.1"
kotlinAndroidExtensions = "2.1.10"
kotlinGradlePlugin = "2.1.10"
kotlinSerialization = "2.1.10"
kotlinxCoroutinesAndroid = "1.8.0"
kotlinxCoroutinesTest = "1.6.1"
kotlinxDatetime = "0.4.0"
kotlinxSerializationJson = "1.5.1"
ktorNetwork = "2.3.10"
library = "1.2.0"
libraryVersion = "3.2"
material = "1.12.0"
okhttp = "3.12.12"
ossLicensesPlugin = "0.10.6"
patternlockview = "1.0.0"
perfPlugin = "1.4.2"
playServicesMlkitFaceDetection = "17.1.0"
playServicesOssLicenses = "17.1.0"
preferenceKtx = "1.2.1"
recyclerview = "1.4.0"
rules = "1.6.1"
runner = "1.6.2"
skyfishjyLibrary = "1.0.1"
dotsindicator = "5.1.0"
workRuntimeKtx = "2.10.0"
[libraries]
activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" }
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
bigimageviewer = { module = "com.github.piasy:BigImageViewer", version.ref = "bigimageviewer" }
biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" }
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
core = { module = "ru.noties.markwon:core", version.ref = "core" }
core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
dirchooser-library = { module = "net.rdrei.android.dirchooser:library", version.ref = "libraryVersion" }
documentfilex = { module = "xyz.quaver:documentfilex", version.ref = "documentfilex" }
espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" }
ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" }
fab = { module = "com.github.clans:fab", version.ref = "fab" }
firebase-analytics-ktx = { module = "com.google.firebase:firebase-analytics-ktx" }
firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
firebase-crashlytics-gradle = { module = "com.google.firebase:firebase-crashlytics-gradle", version.ref = "firebaseCrashlyticsGradle" }
firebase-crashlytics-ktx = { module = "com.google.firebase:firebase-crashlytics-ktx" }
firebase-perf-ktx = { module = "com.google.firebase:firebase-perf-ktx" }
floatingsearchview = { module = "xyz.quaver:floatingsearchview", version.ref = "floatingsearchview" }
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" }
frescoimageloader = { module = "com.github.piasy:FrescoImageLoader", version.ref = "bigimageviewer" }
frescoimageviewfactory = { module = "com.github.piasy:FrescoImageViewFactory", version.ref = "bigimageviewer" }
google-services = { module = "com.google.gms:google-services", version.ref = "googleServices" }
gradle = { module = "com.android.tools.build:gradle", version.ref = "gradle" }
gridlayout = { module = "androidx.gridlayout:gridlayout", version.ref = "gridlayout" }
imagepipeline-okhttp3 = { module = "com.facebook.fresco:imagepipeline-okhttp3", version.ref = "imagepipelineOkhttp3" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
junit = { module = "junit:junit", version.ref = "junit" }
kotlin-android-extensions = { module = "org.jetbrains.kotlin:kotlin-android-extensions", version.ref = "kotlinAndroidExtensions" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" }
kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlinSerialization" }
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
ktor-network = { module = "io.ktor:ktor-network", version.ref = "ktorNetwork" }
library = { module = "com.daimajia.swipelayout:library", version.ref = "library" }
material = { module = "com.google.android.material:material", version.ref = "material" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
oss-licenses-plugin = { module = "com.google.android.gms:oss-licenses-plugin", version.ref = "ossLicensesPlugin" }
patternlockview = { module = "com.github.aritraroy:PatternLockView", version = "master-SNAPSHOT" }
perf-plugin = { module = "com.google.firebase:perf-plugin", version.ref = "perfPlugin" }
play-services-mlkit-face-detection = { module = "com.google.android.gms:play-services-mlkit-face-detection", version.ref = "playServicesMlkitFaceDetection" }
play-services-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "playServicesOssLicenses" }
preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" }
recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
ripplebackground-library = { module = "com.skyfishjy.ripplebackground:library", version.ref = "skyfishjyLibrary" }
rules = { module = "androidx.test:rules", version.ref = "rules" }
runner = { module = "androidx.test:runner", version.ref = "runner" }
dotsindicator = { module = "com.tbuonomo:dotsindicator", version.ref = "dotsindicator" }
work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
recyclerview-fastscroller = { module = "com.quiph.ui:recyclerviewfastscroller", version = "1.0.0" }
pinlockview = { module = "com.github.aritraroy:pinlockview", version = "2.1.0" }
androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version = "1.7.8" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
gms-oss-licenses = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "ossLicensesPlugin" }
gms-google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
firebase-crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "firebaseCrashlyticsGradle" }
firebase-perf = { id = "com.google.firebase.firebase-perf", version.ref = "perfPlugin" }

Binary file not shown.

View File

@@ -1,6 +1,7 @@
#Tue Oct 13 22:37:11 KST 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

297
gradlew vendored Normal file → Executable file
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh #!/bin/sh
# #
# Copyright 2015 the original author or authors. # Copyright © 2015-2021 the original authors.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@@ -15,80 +15,116 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
# #
# SPDX-License-Identifier: Apache-2.0
#
############################################################################## ##############################################################################
## #
## Gradle start up script for UN*X # Gradle start up script for POSIX generated by Gradle.
## #
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
############################################################################## ##############################################################################
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" app_path=$0
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do # Need this for daisy-chained symlinks.
ls=`ls -ld "$PRG"` while
link=`expr "$ls" : '.*-> \(.*\)$'` APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
if expr "$link" : '/.*' > /dev/null; then [ -h "$app_path" ]
PRG="$link" do
else ls=$( ls -ld "$app_path" )
PRG=`dirname "$PRG"`"/$link" link=${ls#*' -> '}
fi case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle" # This is normally unused
APP_BASE_NAME=`basename "$0"` # shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum" MAX_FD=maximum
warn () { warn () {
echo "$*" echo "$*"
} } >&2
die () { die () {
echo echo
echo "$*" echo "$*"
echo echo
exit 1 exit 1
} } >&2
# OS specific support (must be 'true' or 'false'). # OS specific support (must be 'true' or 'false').
cygwin=false cygwin=false
msys=false msys=false
darwin=false darwin=false
nonstop=false nonstop=false
case "`uname`" in case "$( uname )" in #(
CYGWIN* ) CYGWIN* ) cygwin=true ;; #(
cygwin=true Darwin* ) darwin=true ;; #(
;; MSYS* | MINGW* ) msys=true ;; #(
Darwin* ) NONSTOP* ) nonstop=true ;;
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables # IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java" JAVACMD=$JAVA_HOME/jre/sh/java
else else
JAVACMD="$JAVA_HOME/bin/java" JAVACMD=$JAVA_HOME/bin/java
fi fi
if [ ! -x "$JAVACMD" ] ; then if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -97,87 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi fi
else else
JAVACMD="java" JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
MAX_FD_LIMIT=`ulimit -H -n` case $MAX_FD in #(
if [ $? -eq 0 ] ; then max*)
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
MAX_FD="$MAX_FD_LIMIT" # shellcheck disable=SC2039,SC3045
fi MAX_FD=$( ulimit -H -n ) ||
ulimit -n $MAX_FD warn "Could not query maximum file descriptor limit"
if [ $? -ne 0 ] ; then esac
warn "Could not set maximum file descriptor limit: $MAX_FD" case $MAX_FD in #(
fi '' | soft) :;; #(
else *)
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
fi # shellcheck disable=SC2039,SC3045
fi ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac esac
fi fi
# Escape application args # Collect all arguments for the java command, stacking in reverse order:
save () { # * args from the command line
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done # * the main class name
echo " " # * -classpath
} # * -D...appname settings
APP_ARGS=`save "$@"` # * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules # For Cygwin or MSYS, switch paths to Windows format before running java
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@" exec "$JAVACMD" "$@"

194
gradlew.bat vendored
View File

@@ -1,100 +1,94 @@
@rem @rem
@rem Copyright 2015 the original author or authors. @rem Copyright 2015 the original author or authors.
@rem @rem
@rem Licensed under the Apache License, Version 2.0 (the "License"); @rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License. @rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at @rem You may obtain a copy of the License at
@rem @rem
@rem https://www.apache.org/licenses/LICENSE-2.0 @rem https://www.apache.org/licenses/LICENSE-2.0
@rem @rem
@rem Unless required by applicable law or agreed to in writing, software @rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS, @rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and @rem See the License for the specific language governing permissions and
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@rem SPDX-License-Identifier: Apache-2.0
@if "%DEBUG%" == "" @echo off @rem
@rem ##########################################################################
@rem @if "%DEBUG%"=="" @echo off
@rem Gradle startup script for Windows @rem ##########################################################################
@rem @rem
@rem ########################################################################## @rem Gradle startup script for Windows
@rem
@rem Set local scope for the variables with windows NT shell @rem ##########################################################################
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables with windows NT shell
set DIRNAME=%~dp0 if "%OS%"=="Windows_NT" setlocal
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set DIRNAME=%~dp0
set APP_HOME=%DIRNAME% if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set APP_BASE_NAME=%~n0
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" set APP_HOME=%DIRNAME%
@rem Find java.exe @rem Resolve any "." and ".." in APP_HOME to make it shorter.
if defined JAVA_HOME goto findJavaFromJavaHome for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
set JAVA_EXE=java.exe @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
%JAVA_EXE% -version >NUL 2>&1 set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
if "%ERRORLEVEL%" == "0" goto init
@rem Find java.exe
echo. if defined JAVA_HOME goto findJavaFromJavaHome
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo. set JAVA_EXE=java.exe
echo Please set the JAVA_HOME variable in your environment to match the %JAVA_EXE% -version >NUL 2>&1
echo location of your Java installation. if %ERRORLEVEL% equ 0 goto execute
goto fail echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
:findJavaFromJavaHome echo. 1>&2
set JAVA_HOME=%JAVA_HOME:"=% echo Please set the JAVA_HOME variable in your environment to match the 1>&2
set JAVA_EXE=%JAVA_HOME%/bin/java.exe echo location of your Java installation. 1>&2
if exist "%JAVA_EXE%" goto init goto fail
echo. :findJavaFromJavaHome
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% set JAVA_HOME=%JAVA_HOME:"=%
echo. set JAVA_EXE=%JAVA_HOME%/bin/java.exe
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation. if exist "%JAVA_EXE%" goto execute
goto fail echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
:init echo. 1>&2
@rem Get command-line arguments, handling Windows variants echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
if not "%OS%" == "Windows_NT" goto win9xME_args
goto fail
:win9xME_args
@rem Slurp the command line arguments. :execute
set CMD_LINE_ARGS= @rem Setup the command line
set _SKIP=2
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:win9xME_args_slurp
if "x%~1" == "x" goto execute
@rem Execute Gradle
set CMD_LINE_ARGS=%* "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:execute :end
@rem Setup the command line @rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:fail
@rem Execute Gradle rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
:end if %EXIT_CODE% equ 0 set EXIT_CODE=1
@rem End local scope for the variables with windows NT shell if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
if "%ERRORLEVEL%"=="0" goto mainEnd exit /b %EXIT_CODE%
:fail :mainEnd
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of if "%OS%"=="Windows_NT" endlocal
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 :omega
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1 +0,0 @@
include ':app'

32
settings.gradle.kts Normal file
View File

@@ -0,0 +1,32 @@
pluginManagement {
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") {
useModule("com.google.android.gms:oss-licenses-plugin:${requested.version}")
}
}
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
rootProject.name = "Pupil"
include(":app")