Compare commits
24 Commits
5.0-beta1
...
5.0-hotfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8de1429c1 | ||
|
|
3ba6cb81ae | ||
|
|
acc85da80f | ||
|
|
b53de8624d | ||
|
|
6e2eeb29cc | ||
|
|
62eb28ac01 | ||
|
|
fd298529bf | ||
|
|
297ce506b1 | ||
|
|
18c6954be3 | ||
|
|
cea3fb1e65 | ||
|
|
7f274fd238 | ||
|
|
439a8e93ec | ||
|
|
83801feee9 | ||
|
|
8a6860c96e | ||
|
|
5c959f2987 | ||
|
|
4e4397287a | ||
|
|
fe02abc9e8 | ||
|
|
59347ab317 | ||
|
|
f408a91176 | ||
|
|
6f6956ce27 | ||
|
|
4ecad8eccc | ||
|
|
486fbe46a0 | ||
|
|
1ddb636dd0 | ||
|
|
081c890b4e |
5
.idea/jarRepositories.xml
generated
5
.idea/jarRepositories.xml
generated
@@ -56,5 +56,10 @@
|
|||||||
<option name="name" value="MavenLocal" />
|
<option name="name" value="MavenLocal" />
|
||||||
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
|
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
|
||||||
</remote-repository>
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenLocal" />
|
||||||
|
<option name="name" value="MavenLocal" />
|
||||||
|
<option name="url" value="file:/$USER_HOME$/.m2/repository" />
|
||||||
|
</remote-repository>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
1
.idea/vcs.xml
generated
1
.idea/vcs.xml
generated
@@ -2,5 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
|
<mapping directory="$PROJECT_DIR$/gh-pages" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -3,6 +3,7 @@ apply plugin: 'kotlin-android'
|
|||||||
apply plugin: 'kotlin-kapt'
|
apply plugin: 'kotlin-kapt'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
apply plugin: 'kotlinx-serialization'
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
apply plugin: 'com.google.android.gms.oss-licenses-plugin'
|
||||||
|
|
||||||
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
if (file("google-services.json").exists() && file("src/debug/google-services.json").exists()) {
|
||||||
logger.lifecycle("Firebase Enabled")
|
logger.lifecycle("Firebase Enabled")
|
||||||
@@ -14,13 +15,13 @@ if (file("google-services.json").exists() && file("src/debug/google-services.jso
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "xyz.quaver.pupil"
|
applicationId "xyz.quaver.pupil"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode 57
|
versionCode 58
|
||||||
versionName "5.0-beta1"
|
versionName "5.0-hotfix4"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
@@ -63,7 +64,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9"
|
||||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC-HOTFIX1"
|
//implementation "org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0-RC"
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
||||||
implementation 'androidx.preference:preference:1.1.1'
|
implementation 'androidx.preference:preference:1.1.1'
|
||||||
@@ -76,8 +77,10 @@ dependencies {
|
|||||||
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
implementation 'com.google.firebase:firebase-analytics:17.5.0'
|
||||||
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
implementation 'com.google.firebase:firebase-crashlytics:17.2.1'
|
||||||
implementation 'com.google.firebase:firebase-perf:19.0.8'
|
implementation 'com.google.firebase:firebase-perf:19.0.8'
|
||||||
|
implementation 'com.google.android.gms:play-services-oss-licenses:17.0.0'
|
||||||
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
implementation 'com.github.arimorty:floatingsearchview:2.1.1'
|
||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
|
//implementation 'com.quiph.ui:recyclerviewfastscroller:0.2.1'
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||||
@@ -96,10 +99,10 @@ dependencies {
|
|||||||
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
implementation 'com.andrognito.patternlockview:patternlockview:1.0.0'
|
||||||
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
//implementation 'com.andrognito.pinlockview:pinlockview:2.1.0'
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
implementation ("xyz.quaver:libpupil:1.3") {
|
implementation ("xyz.quaver:libpupil:1.6") {
|
||||||
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
exclude group: 'org.jetbrains.kotlinx', module: 'kotlinx-serialization-core-jvm'
|
||||||
}
|
}
|
||||||
implementation "xyz.quaver:documentfilex:0.2.14-alpha2"
|
implementation "xyz.quaver:documentfilex:0.2.15"
|
||||||
testImplementation 'junit:junit:4.13'
|
testImplementation 'junit:junit:4.13'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||||
|
|||||||
BIN
app/libs/kotlinx-serialization-core-1.0.0-RC.jar
Normal file
BIN
app/libs/kotlinx-serialization-core-1.0.0-RC.jar
Normal file
Binary file not shown.
BIN
app/libs/recyclerviewfastscroller-release.aar
Normal file
BIN
app/libs/recyclerviewfastscroller-release.aar
Normal file
Binary file not shown.
@@ -11,8 +11,8 @@
|
|||||||
"type": "SINGLE",
|
"type": "SINGLE",
|
||||||
"filters": [],
|
"filters": [],
|
||||||
"properties": [],
|
"properties": [],
|
||||||
"versionCode": 57,
|
"versionCode": 58,
|
||||||
"versionName": "5.0-beta1",
|
"versionName": "5.0-hotfix4",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"outputFile": "app-release.apk"
|
"outputFile": "app-release.apk"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,26 +20,10 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.rule.ActivityTestRule
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
|
||||||
import xyz.quaver.hiyobi.getReader
|
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
|
||||||
import xyz.quaver.pupil.util.download.Cache
|
|
||||||
import xyz.quaver.pupil.util.download.DownloadWorker
|
|
||||||
import xyz.quaver.pupil.util.getDownloadDirectory
|
|
||||||
import java.io.InputStreamReader
|
|
||||||
import java.net.URL
|
|
||||||
import javax.net.ssl.HttpsURLConnection
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
@@ -54,77 +38,4 @@ class ExampleInstrumentedTest {
|
|||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun checkCacheDir() {
|
|
||||||
val activityTestRule = ActivityTestRule(LockActivity::class.java)
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
|
|
||||||
Runtime.getRuntime().exec("du -hs " + getDownloadDirectory(appContext)).let {
|
|
||||||
InputStreamReader(it.inputStream).readLines().forEach { res ->
|
|
||||||
Log.i("PUPILD", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_nozomi() {
|
|
||||||
val nozomi = getGalleryIDsFromNozomi(null, "index", "all")
|
|
||||||
|
|
||||||
Log.i("PUPILD", nozomi.size.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_doSearch() {
|
|
||||||
val reader = getReader( 1426382)
|
|
||||||
|
|
||||||
val data: ByteArray
|
|
||||||
|
|
||||||
with(URL(createImgList(1426382, reader)[0].path).openConnection() as HttpsURLConnection) {
|
|
||||||
setRequestProperty("User-Agent", user_agent)
|
|
||||||
setRequestProperty("Cookie", cookie)
|
|
||||||
|
|
||||||
data = inputStream.readBytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.d("Pupil", data.size.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_downloadWorker() {
|
|
||||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
|
|
||||||
val galleryID = 515515
|
|
||||||
|
|
||||||
val worker = DownloadWorker.getInstance(context)
|
|
||||||
|
|
||||||
worker.queue.add(galleryID)
|
|
||||||
|
|
||||||
while(worker.progress.indexOfKey(galleryID) < 0 || worker.progress[galleryID] != null) {
|
|
||||||
Log.i("PUPILD", worker.progress[galleryID]?.joinToString(" ") ?: "null")
|
|
||||||
|
|
||||||
if (worker.progress[galleryID]?.all { it.isInfinite() } == true)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("PUPILD", "DONE!!")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_getReaderOrNull() {
|
|
||||||
val context = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
|
|
||||||
val galleryID = 1561552
|
|
||||||
|
|
||||||
runBlocking {
|
|
||||||
Log.i("PUPILD", Cache(context).getReader(galleryID)?.galleryInfo?.title ?: "null")
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("PUPILD", Cache(context).getReaderOrNull(galleryID)?.galleryInfo?.title ?: "null")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun test_suggestion() {
|
|
||||||
getSuggestionsForQuery("female:l")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -19,13 +19,199 @@
|
|||||||
package com.arlib.floatingsearchview
|
package com.arlib.floatingsearchview
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.PorterDuffColorFilter
|
||||||
|
import android.graphics.drawable.Animatable
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.inputmethod.EditorInfo
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
import com.arlib.floatingsearchview.suggestions.SearchSuggestionsAdapter
|
||||||
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
|
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favoriteTags
|
||||||
|
import xyz.quaver.pupil.types.*
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class FloatingSearchViewDayNight @JvmOverloads constructor(
|
class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
context: Context,
|
FloatingSearchView(context, attrs),
|
||||||
attrs: AttributeSet? = null)
|
FloatingSearchView.OnSearchListener,
|
||||||
: FloatingSearchView(context, attrs) {
|
SearchSuggestionsAdapter.OnBindSuggestionCallback,
|
||||||
|
TextWatcher
|
||||||
|
{
|
||||||
|
|
||||||
|
private val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
||||||
|
|
||||||
|
var onHistoryDeleteClickedListener: ((String) -> Unit)? = null
|
||||||
|
var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
||||||
|
|
||||||
|
searchInputView.addTextChangedListener(this)
|
||||||
|
setOnSearchListener(this)
|
||||||
|
setOnBindSuggestionCallback(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
s ?: return
|
||||||
|
|
||||||
|
if (s.any { it.isUpperCase() })
|
||||||
|
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||||
|
when (searchSuggestion) {
|
||||||
|
is TagSuggestion -> {
|
||||||
|
with(searchInputView.text) {
|
||||||
|
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||||
|
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Suggestion -> {
|
||||||
|
with(searchInputView.text) {
|
||||||
|
clear()
|
||||||
|
append(searchSuggestion.str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FavoriteHistorySwitch -> onFavoriteHistorySwitchClickListener?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearchAction(currentQuery: String?) {}
|
||||||
|
|
||||||
|
override fun onBindSuggestion(
|
||||||
|
suggestionView: View?,
|
||||||
|
leftIcon: ImageView?,
|
||||||
|
textView: TextView?,
|
||||||
|
item: SearchSuggestion?,
|
||||||
|
itemPosition: Int
|
||||||
|
) {
|
||||||
|
when(item) {
|
||||||
|
is TagSuggestion -> {
|
||||||
|
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
||||||
|
|
||||||
|
leftIcon?.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
resources,
|
||||||
|
when(item.n) {
|
||||||
|
"female" -> R.drawable.gender_female
|
||||||
|
"male" -> R.drawable.gender_male
|
||||||
|
"language" -> R.drawable.translate
|
||||||
|
"group" -> R.drawable.account_group
|
||||||
|
"character" -> R.drawable.account_star
|
||||||
|
"series" -> R.drawable.book_open
|
||||||
|
"artist" -> R.drawable.brush
|
||||||
|
else -> R.drawable.tag
|
||||||
|
},
|
||||||
|
context.theme)
|
||||||
|
)
|
||||||
|
|
||||||
|
with(suggestionView?.findViewById<ImageView>(R.id.right_icon)) {
|
||||||
|
this ?: return@with
|
||||||
|
|
||||||
|
if (favoriteTags.contains(Tag.parse(tag)))
|
||||||
|
setImageResource(R.drawable.ic_star_filled)
|
||||||
|
else
|
||||||
|
setImageResource(R.drawable.ic_star_empty)
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
rotation = 0f
|
||||||
|
|
||||||
|
isEnabled = true
|
||||||
|
isClickable = true
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
val tag = Tag.parse(tag)
|
||||||
|
|
||||||
|
if (favoriteTags.contains(tag)) {
|
||||||
|
setImageResource(R.drawable.ic_star_empty)
|
||||||
|
favoriteTags.remove(tag)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setImageDrawable(
|
||||||
|
AnimatedVectorDrawableCompat.create(context,
|
||||||
|
R.drawable.avd_star
|
||||||
|
))
|
||||||
|
(drawable as Animatable).start()
|
||||||
|
|
||||||
|
favoriteTags.add(tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.t == -1) {
|
||||||
|
textView?.text = item.s
|
||||||
|
} else {
|
||||||
|
(suggestionView as? LinearLayout)?.let {
|
||||||
|
val count = it.findViewById<TextView>(R.id.count)
|
||||||
|
if (count == null)
|
||||||
|
it.addView(
|
||||||
|
LayoutInflater.from(context).inflate(R.layout.suggestion_count, suggestionView, false)
|
||||||
|
.apply {
|
||||||
|
this as TextView
|
||||||
|
|
||||||
|
text = item.t.toString()
|
||||||
|
}, 2
|
||||||
|
)
|
||||||
|
else
|
||||||
|
count.text = item.t.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is FavoriteHistorySwitch -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.swap_horizontal, context.theme))
|
||||||
|
}
|
||||||
|
is Suggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.history, context.theme))
|
||||||
|
|
||||||
|
with(suggestionView?.findViewById<ImageView>(R.id.right_icon)) {
|
||||||
|
this ?: return@with
|
||||||
|
|
||||||
|
setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.delete, context.theme))
|
||||||
|
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
rotation = 0f
|
||||||
|
|
||||||
|
isEnabled = true
|
||||||
|
isClickable = true
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
onHistoryDeleteClickedListener?.invoke(item.str)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is LoadingSuggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(CircularProgressDrawable(context).also {
|
||||||
|
it.setStyle(CircularProgressDrawable.DEFAULT)
|
||||||
|
it.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN)
|
||||||
|
it.start()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
is NoResultSuggestion -> {
|
||||||
|
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.close, context.theme))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// hack to remove color attributes which should not be reused
|
// hack to remove color attributes which should not be reused
|
||||||
override fun onSaveInstanceState(): Parcelable? {
|
override fun onSaveInstanceState(): Parcelable? {
|
||||||
|
|||||||
@@ -18,10 +18,7 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil
|
package xyz.quaver.pupil
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.*
|
||||||
import android.app.Notification
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
@@ -41,7 +38,9 @@ import okhttp3.Interceptor
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import xyz.quaver.setClient
|
import xyz.quaver.setClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@@ -50,9 +49,13 @@ import kotlin.reflect.KClass
|
|||||||
|
|
||||||
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
typealias PupilInterceptor = (Interceptor.Chain) -> Response
|
||||||
|
|
||||||
lateinit var histories: GalleryList
|
lateinit var histories: SavedSet<Int>
|
||||||
private set
|
private set
|
||||||
lateinit var favorites: GalleryList
|
lateinit var favorites: SavedSet<Int>
|
||||||
|
private set
|
||||||
|
lateinit var favoriteTags: SavedSet<Tag>
|
||||||
|
private set
|
||||||
|
lateinit var searchHistory: SavedSet<String>
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val interceptors = mutableMapOf<KClass<out Any>, PupilInterceptor>()
|
val interceptors = mutableMapOf<KClass<out Any>, PupilInterceptor>()
|
||||||
@@ -97,7 +100,7 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Preferences.get<String>("download_folder").also {
|
Preferences.get<String>("download_folder").also {
|
||||||
if (Build.VERSION.SDK_INT > 19)
|
if (it.startsWith("content") && Build.VERSION.SDK_INT > 19)
|
||||||
contentResolver.takePersistableUriPermission(
|
contentResolver.takePersistableUriPermission(
|
||||||
Uri.parse(it),
|
Uri.parse(it),
|
||||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
@@ -105,13 +108,17 @@ class Pupil : Application() {
|
|||||||
|
|
||||||
if (!FileX(this, it).canWrite())
|
if (!FileX(this, it).canWrite())
|
||||||
throw Exception()
|
throw Exception()
|
||||||
|
|
||||||
|
DownloadManager.getInstance(this).migrate()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Preferences.remove("download_folder")
|
Preferences.remove("download_folder")
|
||||||
}
|
}
|
||||||
|
|
||||||
histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json"))
|
histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0)
|
||||||
favorites = GalleryList(File(ContextCompat.getDataDir(this), "favorites.json"))
|
favorites = SavedSet(File(ContextCompat.getDataDir(this), "favorites.json"), 0)
|
||||||
|
favoriteTags = SavedSet(File(ContextCompat.getDataDir(this), "favorites_tags.json"), Tag.parse(""))
|
||||||
|
searchHistory = SavedSet(File(ContextCompat.getDataDir(this), "search_histories.json"), "")
|
||||||
|
|
||||||
if (Preferences["new_history"]) {
|
if (Preferences["new_history"]) {
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|||||||
@@ -19,9 +19,6 @@
|
|||||||
package xyz.quaver.pupil.adapters
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.PorterDuff
|
|
||||||
import android.graphics.PorterDuffColorFilter
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.util.SparseBooleanArray
|
import android.util.SparseBooleanArray
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@@ -29,23 +26,26 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
import androidx.swiperefreshlayout.widget.CircularProgressDrawable
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
import com.bumptech.glide.RequestManager
|
import com.bumptech.glide.RequestManager
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
|
import com.bumptech.glide.load.engine.GlideException
|
||||||
|
import com.bumptech.glide.request.RequestListener
|
||||||
|
import com.bumptech.glide.request.target.Target
|
||||||
import com.daimajia.swipe.SwipeLayout
|
import com.daimajia.swipe.SwipeLayout
|
||||||
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
|
||||||
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
|
||||||
import com.google.android.material.chip.Chip
|
|
||||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import xyz.quaver.hitomi.getReader
|
import xyz.quaver.hitomi.getReader
|
||||||
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
@@ -88,11 +88,10 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
val imageList = cache.metadata.imageList!!
|
val imageList = cache.metadata.imageList!!
|
||||||
|
|
||||||
progress = imageList.filterNotNull().size
|
progress = imageList.filterNotNull().size
|
||||||
|
max = imageList.size
|
||||||
|
|
||||||
if (visibility == View.GONE) {
|
if (visibility == View.GONE)
|
||||||
visibility = View.VISIBLE
|
visibility = View.VISIBLE
|
||||||
max = imageList.size
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress == max) {
|
if (progress == max) {
|
||||||
val downloadManager = DownloadManager.getInstance(context)
|
val downloadManager = DownloadManager.getInstance(context)
|
||||||
@@ -158,6 +157,28 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
|
|||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.error(R.drawable.image_broken_variant)
|
.error(R.drawable.image_broken_variant)
|
||||||
|
.listener(object: RequestListener<Drawable> {
|
||||||
|
override fun onLoadFailed(
|
||||||
|
e: GlideException?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable>?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean {
|
||||||
|
Cache.getInstance(context, galleryID).let {
|
||||||
|
it.cacheFolder.getChild(".thumbnail").let { if (it.exists()) it.delete() }
|
||||||
|
it.downloadFolder?.getChild(".thumbnail")?.let { if (it.exists()) it.delete() }
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResourceReady(
|
||||||
|
resource: Drawable?,
|
||||||
|
model: Any?,
|
||||||
|
target: Target<Drawable>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
): Boolean = false
|
||||||
|
})
|
||||||
.apply {
|
.apply {
|
||||||
if (BuildConfig.CENSOR)
|
if (BuildConfig.CENSOR)
|
||||||
override(5, 8)
|
override(5, 8)
|
||||||
|
|||||||
@@ -40,10 +40,9 @@ import xyz.quaver.Code
|
|||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.io.util.readBytes
|
import xyz.quaver.io.util.readBytes
|
||||||
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.ui.ReaderActivity
|
import xyz.quaver.pupil.ui.ReaderActivity
|
||||||
@@ -83,14 +82,9 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
cache = Cache.getInstance(holder.view.context, galleryID)
|
cache = Cache.getInstance(holder.view.context, galleryID)
|
||||||
|
|
||||||
if (isFullScreen) {
|
if (isFullScreen) {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.MATCH_PARENT
|
|
||||||
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT
|
||||||
} else {
|
} else {
|
||||||
holder.view.layoutParams.height = RecyclerView.LayoutParams.WRAP_CONTENT
|
holder.view.container.layoutParams.height = ConstraintLayout.LayoutParams.WRAP_CONTENT
|
||||||
holder.view.container.layoutParams.height = 0
|
|
||||||
|
|
||||||
(holder.view.container.layoutParams as ConstraintLayout.LayoutParams)
|
|
||||||
.dimensionRatio = "W,${reader!!.galleryInfo.files[position].width}:${reader!!.galleryInfo.files[position].height}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
holder.view.image.setOnPhotoTapListener { _, _, _ ->
|
||||||
@@ -116,10 +110,7 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
)
|
)
|
||||||
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
|
, LazyHeaders.Builder().addHeader("Referer", getReferer(galleryID)).build())
|
||||||
Code.HIYOBI ->
|
Code.HIYOBI ->
|
||||||
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path, LazyHeaders.Builder()
|
GlideUrl(createImgList(galleryID, reader!!, lowQuality)[position].path)
|
||||||
.addHeader("User-Agent", user_agent)
|
|
||||||
.addHeader("Cookie", cookie)
|
|
||||||
.build())
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
holder.view.image.post {
|
holder.view.image.post {
|
||||||
@@ -127,7 +118,16 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
.load(url!!)
|
.load(url!!)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(false)
|
.skipMemoryCache(false)
|
||||||
.fitCenter()
|
.error(R.drawable.image_broken_variant)
|
||||||
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
else
|
||||||
|
override(
|
||||||
|
holder.view.context.resources.displayMetrics.widthPixels,
|
||||||
|
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
|
||||||
|
)
|
||||||
|
}
|
||||||
.error(R.drawable.image_broken_variant)
|
.error(R.drawable.image_broken_variant)
|
||||||
.into(holder.view.image)
|
.into(holder.view.image)
|
||||||
}
|
}
|
||||||
@@ -138,12 +138,20 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
if (progress?.isInfinite() == true && image != null) {
|
if (progress?.isInfinite() == true && image != null) {
|
||||||
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
holder.view.reader_item_progressbar.visibility = View.INVISIBLE
|
||||||
|
|
||||||
holder.view.image.post {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
glide
|
glide
|
||||||
.load(image.readBytes())
|
.load(image.readBytes())
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.fitCenter()
|
.apply {
|
||||||
|
if (BuildConfig.CENSOR)
|
||||||
|
override(5, 8)
|
||||||
|
else
|
||||||
|
override(
|
||||||
|
holder.view.context.resources.displayMetrics.widthPixels,
|
||||||
|
holder.view.context.resources.getDimensionPixelSize(R.dimen.reader_max_height)
|
||||||
|
)
|
||||||
|
}
|
||||||
.error(R.drawable.image_broken_variant)
|
.error(R.drawable.image_broken_variant)
|
||||||
.listener(object: RequestListener<Drawable> {
|
.listener(object: RequestListener<Drawable> {
|
||||||
override fun onLoadFailed(
|
override fun onLoadFailed(
|
||||||
@@ -155,16 +163,19 @@ class ReaderAdapter(private val activity: ReaderActivity,
|
|||||||
cache!!.metadata.imageList?.set(position, null)
|
cache!!.metadata.imageList?.set(position, null)
|
||||||
image.delete()
|
image.delete()
|
||||||
DownloadService.cancel(holder.view.context, galleryID)
|
DownloadService.cancel(holder.view.context, galleryID)
|
||||||
DownloadService.delete(holder.view.context, galleryID)
|
DownloadService.download(holder.view.context, galleryID, true)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean) =
|
override fun onResourceReady(
|
||||||
false
|
resource: Drawable?,
|
||||||
})
|
model: Any?,
|
||||||
.into(holder.view.image)
|
target: Target<Drawable>?,
|
||||||
|
dataSource: DataSource?,
|
||||||
|
isFirstResource: Boolean
|
||||||
|
) = false
|
||||||
|
}).let { launch(Dispatchers.Main) { it.into(holder.view.image) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
holder.view.reader_item_progressbar.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.util.SparseArray
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.app.TaskStackBuilder
|
import androidx.core.app.TaskStackBuilder
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
@@ -46,7 +47,6 @@ import xyz.quaver.pupil.util.downloader.DownloadManager
|
|||||||
import xyz.quaver.pupil.util.ellipsize
|
import xyz.quaver.pupil.util.ellipsize
|
||||||
import xyz.quaver.pupil.util.normalizeID
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
import xyz.quaver.pupil.util.requestBuilders
|
import xyz.quaver.pupil.util.requestBuilders
|
||||||
import xyz.quaver.pupil.util.startForegroundServiceCompat
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
private typealias ProgressListener = (DownloadService.Tag, Long, Long, Boolean) -> Unit
|
||||||
@@ -103,7 +103,7 @@ class DownloadService : Service() {
|
|||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun notify(galleryID: Int) {
|
private fun notify(galleryID: Int) {
|
||||||
val max = progress[galleryID]?.size ?: 0
|
val max = progress[galleryID]?.size ?: 0
|
||||||
val progress = progress[galleryID]?.count { it.isInfinite() } ?: 0
|
val progress = progress[galleryID]?.count { it == Float.POSITIVE_INFINITY } ?: 0
|
||||||
|
|
||||||
val notification = notification[galleryID] ?: return
|
val notification = notification[galleryID] ?: return
|
||||||
|
|
||||||
@@ -196,7 +196,7 @@ class DownloadService : Service() {
|
|||||||
*/
|
*/
|
||||||
val progress = SparseArray<MutableList<Float>?>()
|
val progress = SparseArray<MutableList<Float>?>()
|
||||||
|
|
||||||
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it.isInfinite() } == true
|
fun isCompleted(galleryID: Int) = progress[galleryID]?.toList()?.all { it == Float.POSITIVE_INFINITY } == true
|
||||||
|
|
||||||
private val callback = object: Callback {
|
private val callback = object: Callback {
|
||||||
|
|
||||||
@@ -215,7 +215,7 @@ class DownloadService : Service() {
|
|||||||
val ext = call.request().url().encodedPath().split('.').last()
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val image = response.body()?.use { it.bytes() } ?: throw Exception()
|
val image = response.also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() } ?: throw Exception()
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
@@ -307,11 +307,16 @@ class DownloadService : Service() {
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progress.indexOfKey(galleryID) < 0)
|
progress.put(galleryID, MutableList(reader.galleryInfo.files.size) { 0F })
|
||||||
progress.put(galleryID, mutableListOf())
|
|
||||||
|
|
||||||
cache.metadata.imageList?.forEach {
|
cache.metadata.imageList?.forEachIndexed { index, image ->
|
||||||
progress[galleryID]?.add(if (it != null) Float.POSITIVE_INFINITY else 0F)
|
progress[galleryID]?.set(index, if (image != null) Float.POSITIVE_INFINITY else 0F)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCompleted(galleryID)) {
|
||||||
|
notificationManager.cancel(galleryID)
|
||||||
|
startId?.let { stopSelf(it) }
|
||||||
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
notification[galleryID]?.setContentTitle(reader.galleryInfo.title?.ellipsize(30))
|
||||||
@@ -328,9 +333,11 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.requestBuilders.filterIndexed { index, _ -> progress[galleryID]?.get(index)?.isInfinite() != true }.forEachIndexed { index, it ->
|
reader.requestBuilders.forEachIndexed { index, it ->
|
||||||
val request = it.tag(Tag(galleryID, index, startId)).build()
|
if (progress[galleryID]?.get(index)?.isInfinite() != true) {
|
||||||
client.newCall(request).enqueue(callback)
|
val request = it.tag(Tag(galleryID, index, startId)).build()
|
||||||
|
client.newCall(request).enqueue(callback)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
queued.forEach { download(it) }
|
queued.forEach { download(it) }
|
||||||
@@ -347,7 +354,7 @@ class DownloadService : Service() {
|
|||||||
const val COMMAND_DELETE = "DELETE"
|
const val COMMAND_DELETE = "DELETE"
|
||||||
|
|
||||||
private fun command(context: Context, extras: Intent.() -> Unit) {
|
private fun command(context: Context, extras: Intent.() -> Unit) {
|
||||||
context.startForegroundServiceCompat(Intent(context, DownloadService::class.java).apply(extras))
|
ContextCompat.startForegroundService(context, Intent(context, DownloadService::class.java).apply(extras))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
fun download(context: Context, galleryID: Int, priority: Boolean = false) {
|
||||||
@@ -374,6 +381,8 @@ class DownloadService : Service() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||||
|
|
||||||
when (intent?.getStringExtra(KEY_COMMAND)) {
|
when (intent?.getStringExtra(KEY_COMMAND)) {
|
||||||
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
COMMAND_DOWNLOAD -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0)
|
||||||
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
download(it, intent.getBooleanExtra(KEY_PRIORITY, false), startId)
|
||||||
|
|||||||
@@ -29,4 +29,25 @@ data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String
|
|||||||
override fun getBody(): String {
|
override fun getBody(): String {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class Suggestion(val str: String) : SearchSuggestion {
|
||||||
|
override fun getBody() = str
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class NoResultSuggestion(val str: String) : SearchSuggestion {
|
||||||
|
override fun getBody() = str
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
class LoadingSuggestion(val str: String) : SearchSuggestion {
|
||||||
|
override fun getBody() = str
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parcelize
|
||||||
|
@Suppress("PARCELABLE_PRIMARY_CONSTRUCTOR_IS_EMPTY")
|
||||||
|
class FavoriteHistorySwitch(private val body: String) : SearchSuggestion {
|
||||||
|
override fun getBody() = body
|
||||||
}
|
}
|
||||||
@@ -19,13 +19,12 @@
|
|||||||
package xyz.quaver.pupil.types
|
package xyz.quaver.pupil.types
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) {
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(tag: String) : Tag {
|
fun parse(tag: String) : Tag {
|
||||||
if (tag.first() == '-') {
|
if (tag.firstOrNull() == '-') {
|
||||||
tag.substring(1).split(Regex(":"), 2).let {
|
tag.substring(1).split(Regex(":"), 2).let {
|
||||||
return when(it.size) {
|
return when(it.size) {
|
||||||
2 -> Tag(it[0], it[1], true)
|
2 -> Tag(it[0], it[1], true)
|
||||||
@@ -63,9 +62,7 @@ data class Tag(val area: String?, val tag: String, val isNegative: Boolean = fal
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode() = toString().hashCode()
|
||||||
return super.hashCode()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags {
|
class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags {
|
||||||
@@ -111,7 +108,4 @@ class Tags(val tags: MutableSet<Tag> = mutableSetOf()) : MutableSet<Tag> by tags
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return tags.joinToString(" ") { it.toString() }
|
return tags.joinToString(" ") { it.toString() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
72
app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt
Normal file
72
app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Pupil, Hitomi.la viewer for Android
|
||||||
|
* Copyright (C) 2020 tom5079
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.util.LockManager
|
||||||
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
|
|
||||||
|
open class BaseActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private var locked: Boolean = true
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
|
||||||
|
super.onCreate(savedInstanceState, persistentState)
|
||||||
|
|
||||||
|
locked = !LockManager(this).locks.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
|
||||||
|
if (Preferences["security_mode"])
|
||||||
|
window.setFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE,
|
||||||
|
WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
else
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
|
|
||||||
|
if (locked)
|
||||||
|
startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID())
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
when(requestCode) {
|
||||||
|
R.id.request_lock.normalizeID() -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK)
|
||||||
|
locked = false
|
||||||
|
else
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -39,6 +39,7 @@ import xyz.quaver.pupil.util.Lock
|
|||||||
import xyz.quaver.pupil.util.LockManager
|
import xyz.quaver.pupil.util.LockManager
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
|
||||||
|
private var lastUnlocked = 0L
|
||||||
class LockActivity : AppCompatActivity() {
|
class LockActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var lockManager: LockManager
|
private lateinit var lockManager: LockManager
|
||||||
@@ -52,6 +53,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
val result = lockManager.check(it)
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
} else
|
} else
|
||||||
@@ -86,6 +88,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
val result = lockManager.check(it)
|
val result = lockManager.check(it)
|
||||||
|
|
||||||
if (result == true) {
|
if (result == true) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(Activity.RESULT_OK)
|
setResult(Activity.RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
@@ -157,6 +160,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
override fun onAuthenticationSucceeded(
|
override fun onAuthenticationSucceeded(
|
||||||
result: BiometricPrompt.AuthenticationResult) {
|
result: BiometricPrompt.AuthenticationResult) {
|
||||||
super.onAuthenticationSucceeded(result)
|
super.onAuthenticationSucceeded(result)
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
setResult(RESULT_OK)
|
setResult(RESULT_OK)
|
||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
@@ -185,6 +189,7 @@ class LockActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mode = intent.getStringExtra("mode")
|
mode = intent.getStringExtra("mode")
|
||||||
|
val force = intent.getBooleanExtra("force", false)
|
||||||
|
|
||||||
when(mode) {
|
when(mode) {
|
||||||
null -> {
|
null -> {
|
||||||
@@ -194,6 +199,13 @@ class LockActivity : AppCompatActivity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - lastUnlocked < 5*60*1000 && !force) {
|
||||||
|
lastUnlocked = System.currentTimeMillis()
|
||||||
|
setResult(RESULT_OK)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Preferences["lock_fingerprint"]
|
Preferences["lock_fingerprint"]
|
||||||
&& BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
&& BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
|||||||
@@ -19,65 +19,49 @@
|
|||||||
package xyz.quaver.pupil.ui
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.drawable.Animatable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.*
|
import android.text.InputType
|
||||||
import android.text.style.AlignmentSpan
|
import android.view.*
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.KeyEvent
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import android.view.WindowManager
|
|
||||||
import android.view.inputmethod.EditorInfo
|
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.cardview.widget.CardView
|
import androidx.cardview.widget.CardView
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.res.ResourcesCompat
|
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
|
||||||
import com.arlib.floatingsearchview.FloatingSearchView
|
import com.arlib.floatingsearchview.FloatingSearchView
|
||||||
import com.arlib.floatingsearchview.FloatingSearchViewDayNight
|
import com.arlib.floatingsearchview.FloatingSearchViewDayNight
|
||||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||||
import com.arlib.floatingsearchview.util.view.SearchInputView
|
import com.arlib.floatingsearchview.util.view.SearchInputView
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.google.android.material.appbar.AppBarLayout
|
import com.google.android.material.appbar.AppBarLayout
|
||||||
|
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.android.synthetic.main.activity_main.*
|
import kotlinx.android.synthetic.main.activity_main.*
|
||||||
import kotlinx.android.synthetic.main.activity_main_content.*
|
import kotlinx.android.synthetic.main.activity_main_content.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import xyz.quaver.hitomi.doSearch
|
import xyz.quaver.hitomi.doSearch
|
||||||
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
import xyz.quaver.hitomi.getGalleryIDsFromNozomi
|
||||||
import xyz.quaver.hitomi.getSuggestionsForQuery
|
import xyz.quaver.hitomi.getSuggestionsForQuery
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.*
|
||||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||||
import xyz.quaver.pupil.favorites
|
|
||||||
import xyz.quaver.pupil.histories
|
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.types.TagSuggestion
|
import xyz.quaver.pupil.types.*
|
||||||
import xyz.quaver.pupil.types.Tags
|
|
||||||
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.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
import java.io.File
|
|
||||||
import java.util.*
|
|
||||||
import kotlin.collections.ArrayList
|
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity :
|
||||||
|
BaseActivity(),
|
||||||
|
FloatingSearchView.OnMenuItemClickListener,
|
||||||
|
NavigationView.OnNavigationItemSelectedListener
|
||||||
|
{
|
||||||
|
|
||||||
enum class Mode {
|
enum class Mode {
|
||||||
SEARCH,
|
SEARCH,
|
||||||
@@ -115,26 +99,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
val lockManager = try {
|
|
||||||
LockManager(this)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
android.app.AlertDialog.Builder(this).apply {
|
|
||||||
setTitle(R.string.warning)
|
|
||||||
setMessage(R.string.lock_corrupted)
|
|
||||||
setPositiveButton(android.R.string.ok) { _, _ ->
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lockManager.isNotEmpty())
|
|
||||||
startActivityForResult(Intent(this, LockActivity::class.java), R.id.request_lock.normalizeID())
|
|
||||||
|
|
||||||
if (intent.action == Intent.ACTION_VIEW) {
|
if (intent.action == Intent.ACTION_VIEW) {
|
||||||
intent.dataString?.let { url ->
|
intent.dataString?.let { url ->
|
||||||
restore(favorites, url,
|
restore(url,
|
||||||
onFailure = {
|
onFailure = {
|
||||||
Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show()
|
||||||
}, onSuccess = {
|
}, onSuccess = {
|
||||||
@@ -176,17 +143,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
(main_recyclerview?.adapter as? GalleryBlockAdapter)?.timer?.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
if (Preferences["security_mode"])
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
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()
|
||||||
@@ -234,10 +190,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
loadBlocks()
|
loadBlocks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
R.id.request_lock.normalizeID() -> {
|
|
||||||
if (resultCode != Activity.RESULT_OK)
|
|
||||||
finish()
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,71 +213,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
//NavigationView
|
//NavigationView
|
||||||
main_nav_view.setNavigationItemSelectedListener {
|
main_nav_view.setNavigationItemSelectedListener(this)
|
||||||
runOnUiThread {
|
|
||||||
main_drawer_layout.closeDrawers()
|
|
||||||
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.main_drawer_home -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.SEARCH
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_history -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.HISTORY
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_downloads -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.DOWNLOAD
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_favorite -> {
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
currentPage = 0
|
|
||||||
query = ""
|
|
||||||
queryStack.clear()
|
|
||||||
mode = Mode.FAVORITE
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
R.id.main_drawer_help -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_github -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_homepage -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_email -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
|
||||||
}
|
|
||||||
R.id.main_drawer_kakaotalk -> {
|
|
||||||
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
with(main_fab_cancel) {
|
with(main_fab_cancel) {
|
||||||
setImageResource(R.drawable.cancel)
|
setImageResource(R.drawable.cancel)
|
||||||
@@ -720,36 +608,24 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var isFavorite = false
|
||||||
|
private val defaultSuggestions: List<SearchSuggestion>
|
||||||
|
get() = when {
|
||||||
|
isFavorite -> {
|
||||||
|
favoriteTags.map {
|
||||||
|
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
||||||
|
} + FavoriteHistorySwitch(getString(R.string.search_show_histories))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
searchHistory.map {
|
||||||
|
Suggestion(it)
|
||||||
|
}.takeLast(20) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
|
||||||
|
}
|
||||||
|
}.reversed()
|
||||||
|
|
||||||
private var suggestionJob : Job? = null
|
private var suggestionJob : Job? = null
|
||||||
private fun setupSearchBar() {
|
private fun setupSearchBar() {
|
||||||
val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
|
||||||
//Change upper case letters to lower case
|
|
||||||
searchInputView.addTextChangedListener(object: TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable?) {
|
|
||||||
s ?: return
|
|
||||||
|
|
||||||
if (s.any { it.isUpperCase() })
|
|
||||||
s.replace(0, s.length, s.toString().toLowerCase(Locale.getDefault()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
|
|
||||||
|
|
||||||
with(main_searchview as FloatingSearchViewDayNight) {
|
with(main_searchview as FloatingSearchViewDayNight) {
|
||||||
val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json")
|
|
||||||
|
|
||||||
if (!favoritesFile.exists()) {
|
|
||||||
favoritesFile.createNewFile()
|
|
||||||
favoritesFile.writeText("[]")
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener {
|
||||||
override fun onMenuOpened() {
|
override fun onMenuOpened() {
|
||||||
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
(this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems()
|
||||||
@@ -760,62 +636,30 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
setOnMenuItemClickListener {
|
onHistoryDeleteClickedListener = {
|
||||||
when(it.itemId) {
|
searchHistory.remove(it)
|
||||||
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), R.id.request_settings.normalizeID())
|
swapSuggestions(defaultSuggestions)
|
||||||
R.id.main_menu_thin -> {
|
|
||||||
main_recyclerview.apply {
|
|
||||||
(adapter as GalleryBlockAdapter).apply {
|
|
||||||
isThin = !isThin
|
|
||||||
}
|
|
||||||
|
|
||||||
adapter = adapter // Force to redraw
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.main_menu_sort_newest -> {
|
|
||||||
sortMode = SortMode.NEWEST
|
|
||||||
it.isChecked = true
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.main_menu_sort_popular -> {
|
|
||||||
sortMode = SortMode.POPULAR
|
|
||||||
it.isChecked = true
|
|
||||||
|
|
||||||
runOnUiThread {
|
|
||||||
currentPage = 0
|
|
||||||
|
|
||||||
cancelFetch()
|
|
||||||
clearGalleries()
|
|
||||||
fetchGalleries(query, sortMode)
|
|
||||||
loadBlocks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
onFavoriteHistorySwitchClickListener = {
|
||||||
|
isFavorite = !isFavorite
|
||||||
|
swapSuggestions(defaultSuggestions)
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnMenuItemClickListener(this@MainActivity)
|
||||||
|
|
||||||
setOnQueryChangeListener { _, query ->
|
setOnQueryChangeListener { _, query ->
|
||||||
this@MainActivity.query = query
|
this@MainActivity.query = query
|
||||||
|
|
||||||
suggestionJob?.cancel()
|
suggestionJob?.cancel()
|
||||||
|
|
||||||
clearSuggestions()
|
|
||||||
|
|
||||||
if (query.isEmpty() or query.endsWith(' ')) {
|
if (query.isEmpty() or query.endsWith(' ')) {
|
||||||
swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
|
swapSuggestions(defaultSuggestions)
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
|
||||||
})
|
|
||||||
|
|
||||||
return@setOnQueryChangeListener
|
return@setOnQueryChangeListener
|
||||||
}
|
}
|
||||||
|
|
||||||
|
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
|
||||||
|
|
||||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||||
|
|
||||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -825,113 +669,22 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
suggestions.filter {
|
suggestions.filter {
|
||||||
val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}"
|
val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}"
|
||||||
Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag)
|
favoriteTags.contains(Tag.parse(tag))
|
||||||
}.reversed().forEach {
|
}.reversed().forEach {
|
||||||
suggestions.remove(it)
|
suggestions.remove(it)
|
||||||
suggestions.add(0, it)
|
suggestions.add(0, it)
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
swapSuggestions(suggestions)
|
swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setOnBindSuggestionCallback { suggestionView, leftIcon, textView, item, _ ->
|
|
||||||
item as TagSuggestion
|
|
||||||
|
|
||||||
val tag = "${item.n}:${item.s.replace(Regex("\\s"), "_")}"
|
|
||||||
|
|
||||||
val color = TypedValue()
|
|
||||||
theme.resolveAttribute(R.attr.colorControlNormal, color, true)
|
|
||||||
|
|
||||||
leftIcon.setImageDrawable(
|
|
||||||
ResourcesCompat.getDrawable(
|
|
||||||
resources,
|
|
||||||
when(item.n) {
|
|
||||||
"female" -> R.drawable.gender_female
|
|
||||||
"male" -> R.drawable.gender_male
|
|
||||||
"language" -> R.drawable.translate
|
|
||||||
"group" -> R.drawable.account_group
|
|
||||||
"character" -> R.drawable.account_star
|
|
||||||
"series" -> R.drawable.book_open
|
|
||||||
"artist" -> R.drawable.brush
|
|
||||||
else -> R.drawable.tag
|
|
||||||
},
|
|
||||||
context.theme)
|
|
||||||
)
|
|
||||||
|
|
||||||
with(suggestionView.findViewById<ImageView>(R.id.right_icon)) {
|
|
||||||
|
|
||||||
if (Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag))
|
|
||||||
setImageResource(R.drawable.ic_star_filled)
|
|
||||||
else
|
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
|
||||||
|
|
||||||
visibility = View.VISIBLE
|
|
||||||
rotation = 0f
|
|
||||||
isEnabled = true
|
|
||||||
|
|
||||||
isClickable = true
|
|
||||||
setOnClickListener {
|
|
||||||
val favorites = Tags(Json.decodeFromString(favoritesFile.readText()))
|
|
||||||
|
|
||||||
if (favorites.contains(tag)) {
|
|
||||||
setImageResource(R.drawable.ic_star_empty)
|
|
||||||
favorites.remove(tag)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
setImageDrawable(AnimatedVectorDrawableCompat.create(context,
|
|
||||||
R.drawable.avd_star
|
|
||||||
))
|
|
||||||
(drawable as Animatable).start()
|
|
||||||
|
|
||||||
favorites.add(tag)
|
|
||||||
}
|
|
||||||
|
|
||||||
favoritesFile.writeText(Json.encodeToString(favorites.tags))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.t == -1) {
|
|
||||||
textView.text = item.s
|
|
||||||
} else {
|
|
||||||
val text = "${item.s}\n ${item.t}"
|
|
||||||
|
|
||||||
val len = text.length
|
|
||||||
val left = item.s.length
|
|
||||||
|
|
||||||
textView.text = SpannableString(text).apply {
|
|
||||||
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
|
||||||
setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
|
||||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
|
||||||
if (searchSuggestion !is TagSuggestion)
|
|
||||||
return
|
|
||||||
|
|
||||||
with(searchInputView.text) {
|
|
||||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
|
||||||
append("${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")} ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSearchAction(currentQuery: String?) {
|
|
||||||
//Do search on onFocusCleared()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||||
override fun onFocus() {
|
override fun onFocus() {
|
||||||
if (query.isEmpty() or query.endsWith(' '))
|
if (query.isEmpty() or query.endsWith(' '))
|
||||||
swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map {
|
swapSuggestions(defaultSuggestions)
|
||||||
TagSuggestion(it.tag, -1, "", it.area ?: "tag")
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onFocusCleared() {
|
override fun onFocusCleared() {
|
||||||
@@ -951,6 +704,113 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActionMenuItemSelected(item: MenuItem?) {
|
||||||
|
when(item?.itemId) {
|
||||||
|
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), R.id.request_settings.normalizeID())
|
||||||
|
R.id.main_menu_thin -> {
|
||||||
|
main_recyclerview.apply {
|
||||||
|
(adapter as GalleryBlockAdapter).apply {
|
||||||
|
isThin = !isThin
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter = adapter // Force to redraw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.main_menu_sort_newest -> {
|
||||||
|
sortMode = SortMode.NEWEST
|
||||||
|
item.isChecked = true
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
R.id.main_menu_sort_popular -> {
|
||||||
|
sortMode = SortMode.POPULAR
|
||||||
|
item.isChecked = true
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
currentPage = 0
|
||||||
|
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNavigationItemSelected(item: MenuItem): Boolean {
|
||||||
|
runOnUiThread {
|
||||||
|
main_drawer_layout.closeDrawers()
|
||||||
|
|
||||||
|
when(item.itemId) {
|
||||||
|
R.id.main_drawer_home -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.SEARCH
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_history -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.HISTORY
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_downloads -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.DOWNLOAD
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_favorite -> {
|
||||||
|
cancelFetch()
|
||||||
|
clearGalleries()
|
||||||
|
currentPage = 0
|
||||||
|
query = ""
|
||||||
|
queryStack.clear()
|
||||||
|
mode = Mode.FAVORITE
|
||||||
|
fetchGalleries(query, sortMode)
|
||||||
|
loadBlocks()
|
||||||
|
}
|
||||||
|
R.id.main_drawer_help -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_github -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_homepage -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.home_page))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_email -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.email))))
|
||||||
|
}
|
||||||
|
R.id.main_drawer_kakaotalk -> {
|
||||||
|
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun cancelFetch() {
|
private fun cancelFetch() {
|
||||||
galleryIDs?.cancel()
|
galleryIDs?.cancel()
|
||||||
loadingJob?.cancel()
|
loadingJob?.cancel()
|
||||||
@@ -974,6 +834,9 @@ class MainActivity : AppCompatActivity() {
|
|||||||
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
private fun fetchGalleries(query: String, sortMode: SortMode) {
|
||||||
val defaultQuery: String = Preferences["default_query"]
|
val defaultQuery: String = Preferences["default_query"]
|
||||||
|
|
||||||
|
if (query.isNotBlank())
|
||||||
|
searchHistory.add(query)
|
||||||
|
|
||||||
if (query != queryStack.lastOrNull()) {
|
if (query != queryStack.lastOrNull()) {
|
||||||
queryStack.remove(query)
|
queryStack.remove(query)
|
||||||
queryStack.add(query)
|
queryStack.add(query)
|
||||||
@@ -981,7 +844,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
if (query.isNotEmpty() && mode != Mode.SEARCH) {
|
||||||
Snackbar.make(this@MainActivity.main_recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
Snackbar.make(this@MainActivity.main_recyclerview, R.string.search_all, Snackbar.LENGTH_SHORT).apply {
|
||||||
setAction(android.R.string.yes) {
|
setAction(android.R.string.ok) {
|
||||||
cancelFetch()
|
cancelFetch()
|
||||||
clearGalleries()
|
clearGalleries()
|
||||||
currentPage = 0
|
currentPage = 0
|
||||||
@@ -1034,12 +897,12 @@ class MainActivity : AppCompatActivity() {
|
|||||||
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
val downloads = DownloadManager.getInstance(this@MainActivity).downloadFolderMap.keys.toList()
|
||||||
|
|
||||||
when {
|
when {
|
||||||
query.isEmpty() -> downloads.also {
|
query.isEmpty() -> downloads.reversed().also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query).sorted()
|
||||||
downloads.filter { result.binarySearch(it) >= 0 }.also {
|
downloads.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1052,7 +915,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val result = doSearch(query).sorted()
|
val result = doSearch(query).sorted()
|
||||||
favorites.filter { result.binarySearch(it) >= 0 }.also {
|
favorites.reversed().filter { result.binarySearch(it) >= 0 }.also {
|
||||||
totalItems = it.size
|
totalItems = it.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1105,4 +968,14 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLowMemory() {
|
||||||
|
super.onLowMemory()
|
||||||
|
Glide.get(this).onLowMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
super.onTrimMemory(level)
|
||||||
|
Glide.get(this).onTrimMemory(level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ import android.os.IBinder
|
|||||||
import android.view.*
|
import android.view.*
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
@@ -36,6 +35,7 @@ import androidx.recyclerview.widget.PagerSnapHelper
|
|||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
|
||||||
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
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.android.synthetic.main.activity_reader.*
|
import kotlinx.android.synthetic.main.activity_reader.*
|
||||||
@@ -57,7 +57,7 @@ import java.util.*
|
|||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
import kotlin.concurrent.timer
|
import kotlin.concurrent.timer
|
||||||
|
|
||||||
class ReaderActivity : AppCompatActivity() {
|
class ReaderActivity : BaseActivity() {
|
||||||
|
|
||||||
private var galleryID = 0
|
private var galleryID = 0
|
||||||
private var currentPage = 0
|
private var currentPage = 0
|
||||||
@@ -101,10 +101,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
title = getString(R.string.reader_loading)
|
title = getString(R.string.reader_loading)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
supportActionBar?.setDisplayHomeAsUpEnabled(false)
|
||||||
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
handleIntent(intent)
|
handleIntent(intent)
|
||||||
cache = Cache.getInstance(this, galleryID)
|
cache = Cache.getInstance(this, galleryID)
|
||||||
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID)
|
||||||
@@ -113,6 +109,7 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
onBackPressed()
|
onBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Preferences["cache_disable"]) {
|
if (Preferences["cache_disable"]) {
|
||||||
reader_download_progressbar.visibility = View.GONE
|
reader_download_progressbar.visibility = View.GONE
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
@@ -171,17 +168,6 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
|
||||||
if (Preferences["security_mode"])
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
menuInflater.inflate(R.menu.reader, menu)
|
menuInflater.inflate(R.menu.reader, menu)
|
||||||
|
|
||||||
@@ -196,8 +182,8 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when(item?.itemId) {
|
when(item.itemId) {
|
||||||
R.id.reader_menu_page_indicator -> {
|
R.id.reader_menu_page_indicator -> {
|
||||||
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, reader_layout, false)
|
||||||
with(view.dialog_number_picker) {
|
with(view.dialog_number_picker) {
|
||||||
@@ -488,4 +474,14 @@ class ReaderActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLowMemory() {
|
||||||
|
super.onLowMemory()
|
||||||
|
Glide.get(this).onLowMemory()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTrimMemory(level: Int) {
|
||||||
|
super.onTrimMemory(level)
|
||||||
|
Glide.get(this).onTrimMemory(level)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -22,34 +22,25 @@ import android.annotation.SuppressLint
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import kotlinx.android.synthetic.main.settings_activity.*
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import net.rdrei.android.dirchooser.DirectoryChooserActivity
|
|
||||||
import xyz.quaver.io.FileX
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.favorites
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
import xyz.quaver.pupil.ui.fragment.LockSettingsFragment
|
||||||
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
import xyz.quaver.pupil.ui.fragment.SettingsFragment
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.Preferences
|
||||||
import java.io.File
|
import xyz.quaver.pupil.util.normalizeID
|
||||||
import java.nio.charset.Charset
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : BaseActivity() {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
|
|
||||||
setContentView(R.layout.settings_activity)
|
setContentView(R.layout.settings_activity)
|
||||||
supportFragmentManager
|
supportFragmentManager
|
||||||
.beginTransaction()
|
.beginTransaction()
|
||||||
@@ -58,66 +49,14 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
if (Preferences["security_mode"])
|
when (item.itemId) {
|
||||||
window.setFlags(
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE,
|
|
||||||
WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
else
|
|
||||||
window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
|
||||||
super.onResume()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
|
||||||
when (item?.itemId) {
|
|
||||||
android.R.id.home -> onBackPressed()
|
android.R.id.home -> onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
|
||||||
when(requestCode) {
|
|
||||||
R.id.request_lock.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
supportFragmentManager
|
|
||||||
.beginTransaction()
|
|
||||||
.replace(R.id.settings, LockSettingsFragment())
|
|
||||||
.addToBackStack("Lock")
|
|
||||||
.commitAllowingStateLoss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
R.id.request_restore.normalizeID() -> {
|
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
|
||||||
val uri = data?.data ?: return
|
|
||||||
|
|
||||||
try {
|
|
||||||
val str = contentResolver.openInputStream(uri).use { inputStream ->
|
|
||||||
inputStream!!
|
|
||||||
|
|
||||||
inputStream.readBytes().toString(Charset.defaultCharset())
|
|
||||||
}
|
|
||||||
|
|
||||||
favorites.addAll(Json.decodeFromString<List<Int>>(str).also {
|
|
||||||
Snackbar.make(
|
|
||||||
window.decorView,
|
|
||||||
getString(R.string.settings_restore_success, it.size),
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
})
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Snackbar.make(
|
|
||||||
window.decorView,
|
|
||||||
R.string.settings_restore_failed,
|
|
||||||
Snackbar.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("InlinedApi")
|
@SuppressLint("InlinedApi")
|
||||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
when (requestCode) {
|
when (requestCode) {
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
.setTitle(R.string.settings_download_folder)
|
.setTitle(R.string.settings_download_folder)
|
||||||
.setView(build())
|
.setView(build())
|
||||||
.setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
.setPositiveButton(requireContext().getText(android.R.string.ok)) { _, _ ->
|
||||||
|
if (Preferences["download_folder", ""].isEmpty())
|
||||||
|
Preferences["download_folder"] = context?.getExternalFilesDir(null)?.toUri()?.toString() ?: ""
|
||||||
|
|
||||||
DownloadManager.getInstance(requireContext()).migrate()
|
DownloadManager.getInstance(requireContext()).migrate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +149,7 @@ class DownloadLocationDialogFragment : DialogFragment() {
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
|
||||||
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
context.contentResolver.takePersistableUriPermission(uri, takeFlags)
|
||||||
|
|
||||||
if (FileX(context, uri).canWrite())
|
if (kotlin.runCatching { FileX(context, uri).canWrite() }.getOrDefault(false))
|
||||||
Preferences["download_folder"] = uri.toString()
|
Preferences["download_folder"] = uri.toString()
|
||||||
else {
|
else {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ class MirrorDialog(context: Context) : AlertDialog(context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onItemMoved = {
|
onItemMoved = {
|
||||||
Preferences["mirrors", it.joinToString(">")]
|
Preferences["mirrors"] = it.joinToString(">")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,11 +74,11 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
lockManager.remove(Lock.Type.PATTERN)
|
lockManager.remove(Lock.Type.PATTERN)
|
||||||
onResume()
|
onResume()
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
@@ -107,11 +107,11 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_lock_remove_message)
|
setMessage(R.string.settings_lock_remove_message)
|
||||||
|
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
lockManager.remove(Lock.Type.PIN)
|
lockManager.remove(Lock.Type.PIN)
|
||||||
onResume()
|
onResume()
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
} else {
|
} else {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
|
|||||||
@@ -62,11 +62,16 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.code() != 200) {
|
||||||
|
response.close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
Intent(Intent.ACTION_SEND).apply {
|
Intent(Intent.ACTION_SEND).apply {
|
||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
putExtra(Intent.EXTRA_TEXT, response.body()?.use { it.string() }?.replace("\n", ""))
|
||||||
}.let {
|
}.let {
|
||||||
context.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
getContext()?.startActivity(Intent.createChooser(it, getString(R.string.settings_backup_share)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -82,7 +87,7 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() {
|
|||||||
.setTitle(R.string.settings_restore_title)
|
.setTitle(R.string.settings_restore_title)
|
||||||
.setView(editText)
|
.setView(editText)
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
restore(favorites, editText.text.toString(),
|
restore(editText.text.toString(),
|
||||||
onFailure = onFailure@{
|
onFailure = onFailure@{
|
||||||
val view = view ?: return@onFailure
|
val view = view ?: return@onFailure
|
||||||
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
|
Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show()
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_cache_alert_message)
|
setMessage(R.string.settings_clear_cache_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
if (dir.exists())
|
if (dir.exists())
|
||||||
dir.deleteRecursively()
|
dir.deleteRecursively()
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"delete_downloads" -> {
|
"delete_downloads" -> {
|
||||||
@@ -83,7 +83,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_downloads_alert_message)
|
setMessage(R.string.settings_clear_downloads_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
@@ -91,7 +91,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dir.exists())
|
if (dir.exists())
|
||||||
dir.listFiles()?.forEach { (it as FileX).deleteRecursively() }
|
dir.listFiles()?.forEach { (it as? FileX)?.deleteRecursively() }
|
||||||
|
|
||||||
job = launch {
|
job = launch {
|
||||||
var size = 0L
|
var size = 0L
|
||||||
@@ -109,18 +109,18 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"clear_history" -> {
|
"clear_history" -> {
|
||||||
AlertDialog.Builder(context).apply {
|
AlertDialog.Builder(context).apply {
|
||||||
setTitle(R.string.warning)
|
setTitle(R.string.warning)
|
||||||
setMessage(R.string.settings_clear_history_alert_message)
|
setMessage(R.string.settings_clear_history_alert_message)
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
histories.clear()
|
histories.clear()
|
||||||
summary = context.getString(R.string.settings_clear_history_summary, histories.size)
|
summary = context.getString(R.string.settings_clear_history_summary, histories.size)
|
||||||
}
|
}
|
||||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.fragment
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
@@ -26,14 +27,20 @@ import androidx.preference.Preference
|
|||||||
import androidx.preference.PreferenceCategory
|
import androidx.preference.PreferenceCategory
|
||||||
import androidx.preference.PreferenceFragmentCompat
|
import androidx.preference.PreferenceFragmentCompat
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||||
|
import com.google.android.material.snackbar.Snackbar
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
import xyz.quaver.pupil.ui.SettingsActivity
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
import xyz.quaver.pupil.ui.dialog.*
|
import xyz.quaver.pupil.ui.dialog.*
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
class SettingsFragment :
|
class SettingsFragment :
|
||||||
PreferenceFragmentCompat(),
|
PreferenceFragmentCompat(),
|
||||||
@@ -68,7 +75,7 @@ class SettingsFragment :
|
|||||||
checkUpdate(activity as SettingsActivity, true)
|
checkUpdate(activity as SettingsActivity, true)
|
||||||
}
|
}
|
||||||
"download_folder" -> {
|
"download_folder" -> {
|
||||||
DownloadLocationDialogFragment().show(requireActivity().supportFragmentManager, "Download Location Dialog")
|
DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog")
|
||||||
}
|
}
|
||||||
"default_query" -> {
|
"default_query" -> {
|
||||||
DefaultQueryDialog(requireContext()).apply {
|
DefaultQueryDialog(requireContext()).apply {
|
||||||
@@ -79,8 +86,10 @@ class SettingsFragment :
|
|||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
"app_lock" -> {
|
"app_lock" -> {
|
||||||
val intent = Intent(requireContext(), LockActivity::class.java)
|
val intent = Intent(requireContext(), LockActivity::class.java).apply {
|
||||||
activity?.startActivityForResult(intent, R.id.request_lock.normalizeID())
|
putExtra("force", true)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, R.id.request_lock.normalizeID())
|
||||||
}
|
}
|
||||||
"mirrors" -> {
|
"mirrors" -> {
|
||||||
MirrorDialog(requireContext())
|
MirrorDialog(requireContext())
|
||||||
@@ -246,10 +255,31 @@ class SettingsFragment :
|
|||||||
summary = Preferences.get<String>("user_id")
|
summary = Preferences.get<String>("user_id")
|
||||||
onPreferenceClickListener = this@SettingsFragment
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
}
|
}
|
||||||
|
"oss" -> {
|
||||||
|
setOnPreferenceClickListener {
|
||||||
|
context?.startActivity(Intent(context, OssLicensesMenuActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
|
when(requestCode) {
|
||||||
|
R.id.request_lock.normalizeID() -> {
|
||||||
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
|
parentFragmentManager
|
||||||
|
.beginTransaction()
|
||||||
|
.replace(R.id.settings, LockSettingsFragment())
|
||||||
|
.addToBackStack("Lock")
|
||||||
|
.commitAllowingStateLoss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.ui.view
|
package xyz.quaver.pupil.ui.view
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
@@ -25,7 +26,16 @@ import xyz.quaver.pupil.R
|
|||||||
import xyz.quaver.pupil.types.Tag
|
import xyz.quaver.pupil.types.Tag
|
||||||
import xyz.quaver.pupil.util.wordCapitalize
|
import xyz.quaver.pupil.util.wordCapitalize
|
||||||
|
|
||||||
class TagChip(context: Context, val tag: Tag) : Chip(context) {
|
@SuppressLint("ViewConstructor")
|
||||||
|
class TagChip(context: Context, tag: Tag) : Chip(context) {
|
||||||
|
|
||||||
|
val tag: Tag =
|
||||||
|
tag.let {
|
||||||
|
when {
|
||||||
|
it.area != null -> it
|
||||||
|
else -> Tag("tag", tag.tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val languages = context.resources.getStringArray(R.array.languages).map {
|
private val languages = context.resources.getStringArray(R.array.languages).map {
|
||||||
it.split("|").let { split ->
|
it.split("|").let { split ->
|
||||||
@@ -34,13 +44,6 @@ class TagChip(context: Context, val tag: Tag) : Chip(context) {
|
|||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val tag = tag.let {
|
|
||||||
when {
|
|
||||||
it.area != null -> it
|
|
||||||
else -> Tag("tag", tag.tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chipIcon = when(tag.area) {
|
chipIcon = when(tag.area) {
|
||||||
"male" -> {
|
"male" -> {
|
||||||
setChipBackgroundColorResource(R.color.material_blue_700)
|
setChipBackgroundColorResource(R.color.material_blue_700)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Pupil, Hitomi.la viewer for Android
|
* Pupil, Hitomi.la viewer for Android
|
||||||
* Copyright (C) 2019 tom5079
|
* Copyright (C) 2020 tom5079
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@@ -18,12 +18,17 @@
|
|||||||
|
|
||||||
package xyz.quaver.pupil.util
|
package xyz.quaver.pupil.util
|
||||||
|
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class GalleryList(private val file: File, private val list: MutableSet<Int> = mutableSetOf()) : MutableSet<Int> by list {
|
class SavedSet <T: Any> (private val file: File, private val any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
val serializer: KSerializer<List<T>>
|
||||||
|
get() = ListSerializer(serializer(any::class.java) as KSerializer<T>)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
@@ -35,45 +40,48 @@ class GalleryList(private val file: File, private val list: MutableSet<Int> = mu
|
|||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
list.clear()
|
set.clear()
|
||||||
list.addAll(
|
kotlin.runCatching {
|
||||||
Json.decodeFromString<List<Int>>(file.bufferedReader().use { it.readText() })
|
Json.decodeFromString(serializer, file.readText())
|
||||||
)
|
}.onSuccess {
|
||||||
|
set.addAll(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
fun save() {
|
fun save() {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
file.writeText(Json.encodeToString(list))
|
file.writeText(Json.encodeToString(serializer, set.toList()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun add(element: Int): Boolean {
|
override fun add(element: T): Boolean {
|
||||||
load()
|
load()
|
||||||
|
|
||||||
return list.add(element).also {
|
return set.add(element).also {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addAll(elements: Collection<Int>): Boolean {
|
override fun addAll(elements: Collection<T>): Boolean {
|
||||||
load()
|
load()
|
||||||
|
|
||||||
return list.addAll(elements).also {
|
return set.addAll(elements).also {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(element: Int): Boolean {
|
override fun remove(element: T): Boolean {
|
||||||
load()
|
load()
|
||||||
|
|
||||||
return list.remove(element).also {
|
return set.remove(element).also {
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
list.clear()
|
set.clear()
|
||||||
save()
|
save()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ import java.io.FileOutputStream
|
|||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use downloader.Cache instead")
|
@Deprecated("Use downloader.Cache instead")
|
||||||
class Cache(context: Context) : ContextWrapper(context) {
|
class Cache(context: Context) : ContextWrapper(context) {
|
||||||
|
|
||||||
|
|||||||
@@ -37,9 +37,7 @@ import xyz.quaver.Code
|
|||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.interceptors
|
import xyz.quaver.pupil.interceptors
|
||||||
@@ -48,6 +46,7 @@ import java.io.File
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.LinkedBlockingQueue
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use DownloadService instead")
|
@Deprecated("Use DownloadService instead")
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
|
class DownloadWorker private constructor(context: Context) : ContextWrapper(context) {
|
||||||
@@ -219,8 +218,6 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
}
|
}
|
||||||
Code.HIYOBI -> {
|
Code.HIYOBI -> {
|
||||||
url(createImgList(galleryID, reader, lowQuality)[index].path)
|
url(createImgList(galleryID, reader, lowQuality)[index].path)
|
||||||
addHeader("User-Agent", user_agent)
|
|
||||||
addHeader("Cookie", cookie)
|
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
//shouldn't be called anyway
|
//shouldn't be called anyway
|
||||||
@@ -280,6 +277,12 @@ class DownloadWorker private constructor(context: Context) : ContextWrapper(cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
if (response.code() != 200) {
|
||||||
|
response.close()
|
||||||
|
onFailure(call, IOException())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val ext = call.request().url().encodedPath().split('.').last()
|
val ext = call.request().url().encodedPath().split('.').last()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import kotlinx.serialization.Serializable
|
|||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use downloader.Cache.Metadata instead")
|
@Deprecated("Use downloader.Cache.Metadata instead")
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
|
|||||||
@@ -34,12 +34,10 @@ import xyz.quaver.Code
|
|||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.io.util.readBytes
|
|
||||||
import xyz.quaver.io.util.readText
|
|
||||||
import xyz.quaver.io.util.writeBytes
|
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
import xyz.quaver.pupil.util.Preferences
|
import xyz.quaver.pupil.util.Preferences
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Metadata(
|
data class Metadata(
|
||||||
@@ -60,6 +58,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
instances[galleryID] ?: Cache(context, galleryID).also { instances.put(galleryID, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
fun delete(galleryID: Int) {
|
fun delete(galleryID: Int) {
|
||||||
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
instances[galleryID]?.cacheFolder?.deleteRecursively()
|
||||||
instances.delete(galleryID)
|
instances.delete(galleryID)
|
||||||
@@ -86,11 +85,11 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findFile(fileName: String): FileX? =
|
fun findFile(fileName: String): FileX? =
|
||||||
cacheFolder.getChild(fileName).let {
|
downloadFolder?.let { downloadFolder -> downloadFolder.getChild(fileName).let {
|
||||||
if (it.exists()) it else null
|
if (it.exists()) it else null
|
||||||
} ?: downloadFolder?.let { downloadFolder -> downloadFolder.getChild(fileName).let {
|
} } ?: cacheFolder.getChild(fileName).let {
|
||||||
if (it.exists()) it else null
|
if (it.exists()) it else null
|
||||||
} }
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
@Suppress("BlockingMethodInNonBlockingContext")
|
||||||
fun setMetadata(change: (Metadata) -> Unit) {
|
fun setMetadata(change: (Metadata) -> Unit) {
|
||||||
@@ -140,7 +139,7 @@ class Cache private constructor(context: Context, val galleryID: Int) : ContextW
|
|||||||
.url(it)
|
.url(it)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
client.newCall(request).execute().body()?.use { it.bytes() }
|
client.newCall(request).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.bytes() }
|
||||||
}.getOrNull()?.also { kotlin.run {
|
}.getOrNull()?.also { kotlin.run {
|
||||||
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
cacheFolder.getChild(".thumbnail").writeBytes(it)
|
||||||
} }
|
} }
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
data ?: {
|
data ?: {
|
||||||
file.createNewFile()
|
file.createNewFile()
|
||||||
file.writeText("{}")
|
|
||||||
mutableMapOf<Int, String>()
|
mutableMapOf<Int, String>()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
}.invoke()
|
}.invoke()
|
||||||
@@ -99,9 +98,6 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun addDownloadFolder(galleryID: Int) {
|
fun addDownloadFolder(galleryID: Int) {
|
||||||
if (downloadFolderMap.containsKey(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
val name = runBlocking {
|
val name = runBlocking {
|
||||||
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
Cache.getInstance(this@DownloadManager, galleryID).getGalleryBlock()
|
||||||
}?.formatDownloadFolder() ?: return
|
}?.formatDownloadFolder() ?: return
|
||||||
@@ -113,19 +109,18 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
downloadFolderMap[galleryID] = folder.name
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|
||||||
|
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() }
|
||||||
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
fun deleteDownloadFolder(galleryID: Int) {
|
fun deleteDownloadFolder(galleryID: Int) {
|
||||||
if (!downloadFolderMap.containsKey(galleryID))
|
|
||||||
return
|
|
||||||
|
|
||||||
downloadFolderMap[galleryID]?.let {
|
downloadFolderMap[galleryID]?.let {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
downloadFolder.getChild(it).delete()
|
downloadFolder.getChild(it).deleteRecursively()
|
||||||
downloadFolderMap.remove(galleryID)
|
downloadFolderMap.remove(galleryID)
|
||||||
|
|
||||||
|
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile() }
|
||||||
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
downloadFolder.getChild(".download").writeText(Json.encodeToString(downloadFolderMap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import java.io.FileOutputStream
|
|||||||
import java.lang.reflect.Array
|
import java.lang.reflect.Array
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use downloader.Cache instead")
|
@Deprecated("Use downloader.Cache instead")
|
||||||
fun getCachedGallery(context: Context, galleryID: Int) =
|
fun getCachedGallery(context: Context, galleryID: Int) =
|
||||||
File(getDownloadDirectory(context), galleryID.toString()).let {
|
File(getDownloadDirectory(context), galleryID.toString()).let {
|
||||||
@@ -36,6 +37,7 @@ fun getCachedGallery(context: Context, galleryID: Int) =
|
|||||||
File(context.cacheDir, "imageCache/$galleryID")
|
File(context.cacheDir, "imageCache/$galleryID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use downloader.Cache instead")
|
@Deprecated("Use downloader.Cache instead")
|
||||||
fun getDownloadDirectory(context: Context) =
|
fun getDownloadDirectory(context: Context) =
|
||||||
Preferences.get<String>("dl_location").let {
|
Preferences.get<String>("dl_location").let {
|
||||||
@@ -45,6 +47,7 @@ fun getDownloadDirectory(context: Context) =
|
|||||||
context.getExternalFilesDir(null)!!
|
context.getExternalFilesDir(null)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
@Deprecated("Use FileX instead")
|
@Deprecated("Use FileX instead")
|
||||||
fun File.isParentOf(another: File) =
|
fun File.isParentOf(another: File) =
|
||||||
another.absolutePath.startsWith(this.absolutePath)
|
another.absolutePath.startsWith(this.absolutePath)
|
||||||
@@ -22,11 +22,7 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import androidx.core.content.ContextCompat
|
||||||
import kotlinx.coroutines.coroutineScope
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import xyz.quaver.Code
|
import xyz.quaver.Code
|
||||||
@@ -34,11 +30,7 @@ import xyz.quaver.hitomi.GalleryBlock
|
|||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
import xyz.quaver.hitomi.getReferer
|
import xyz.quaver.hitomi.getReferer
|
||||||
import xyz.quaver.hitomi.imageUrlFromImage
|
import xyz.quaver.hitomi.imageUrlFromImage
|
||||||
import xyz.quaver.hiyobi.cookie
|
|
||||||
import xyz.quaver.hiyobi.createImgList
|
import xyz.quaver.hiyobi.createImgList
|
||||||
import xyz.quaver.hiyobi.user_agent
|
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
|
||||||
import xyz.quaver.pupil.util.downloader.Metadata
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@@ -101,21 +93,14 @@ fun GalleryBlock.formatDownloadFolder(): String =
|
|||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}
|
}.replace("/", "")
|
||||||
|
|
||||||
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
fun GalleryBlock.formatDownloadFolderTest(format: String): String =
|
||||||
format.let {
|
format.let {
|
||||||
formatMap.entries.fold(it) { str, (k, v) ->
|
formatMap.entries.fold(it) { str, (k, v) ->
|
||||||
str.replace(k, v.invoke(this), true)
|
str.replace(k, v.invoke(this), true)
|
||||||
}
|
}
|
||||||
}
|
}.replace("/", "")
|
||||||
|
|
||||||
fun Context.startForegroundServiceCompat(service: Intent) {
|
|
||||||
if (Build.VERSION.SDK_INT >= 26)
|
|
||||||
startForegroundService(service)
|
|
||||||
else
|
|
||||||
startService(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
val Reader.requestBuilders: List<Request.Builder>
|
val Reader.requestBuilders: List<Request.Builder>
|
||||||
get() {
|
get() {
|
||||||
@@ -134,8 +119,6 @@ val Reader.requestBuilders: List<Request.Builder>
|
|||||||
createImgList(galleryID, this, lowQuality).map {
|
createImgList(galleryID, this, lowQuality).map {
|
||||||
Request.Builder()
|
Request.Builder()
|
||||||
.url(it.path)
|
.url(it.path)
|
||||||
.header("User-Agent", user_agent)
|
|
||||||
.header("Cookie", cookie)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import android.app.PendingIntent
|
|||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -32,11 +33,13 @@ import androidx.appcompat.app.AlertDialog
|
|||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.*
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
@@ -45,14 +48,18 @@ import okhttp3.Response
|
|||||||
import ru.noties.markwon.Markwon
|
import ru.noties.markwon.Markwon
|
||||||
import xyz.quaver.hitomi.GalleryBlock
|
import xyz.quaver.hitomi.GalleryBlock
|
||||||
import xyz.quaver.hitomi.Reader
|
import xyz.quaver.hitomi.Reader
|
||||||
|
import xyz.quaver.hitomi.getGalleryBlock
|
||||||
|
import xyz.quaver.hitomi.getReader
|
||||||
import xyz.quaver.io.FileX
|
import xyz.quaver.io.FileX
|
||||||
import xyz.quaver.io.util.getChild
|
import xyz.quaver.io.util.getChild
|
||||||
import xyz.quaver.io.util.*
|
import xyz.quaver.io.util.*
|
||||||
import xyz.quaver.pupil.BuildConfig
|
import xyz.quaver.pupil.BuildConfig
|
||||||
import xyz.quaver.pupil.R
|
import xyz.quaver.pupil.R
|
||||||
import xyz.quaver.pupil.client
|
import xyz.quaver.pupil.client
|
||||||
|
import xyz.quaver.pupil.favorites
|
||||||
import xyz.quaver.pupil.services.DownloadService
|
import xyz.quaver.pupil.services.DownloadService
|
||||||
import xyz.quaver.pupil.util.downloader.Cache
|
import xyz.quaver.pupil.util.downloader.Cache
|
||||||
|
import xyz.quaver.pupil.util.downloader.Metadata
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
@@ -152,7 +159,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
setTitle(R.string.update_title)
|
setTitle(R.string.update_title)
|
||||||
val msg = extractReleaseNote(update, Locale.getDefault())
|
val msg = extractReleaseNote(update, Locale.getDefault())
|
||||||
setMessage(Markwon.create(context).toMarkdown(msg))
|
setMessage(Markwon.create(context).toMarkdown(msg))
|
||||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
|
||||||
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
|
||||||
@@ -175,7 +182,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
Preferences["update_download_id"] = it
|
Preferences["update_download_id"] = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setNegativeButton(if (force) android.R.string.no else R.string.ignore_update) { _, _ ->
|
setNegativeButton(if (force) android.R.string.cancel else R.string.ignore_update) { _, _ ->
|
||||||
if (!force)
|
if (!force)
|
||||||
preferences.edit()
|
preferences.edit()
|
||||||
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
|
.putLong("ignore_update_until", System.currentTimeMillis() + 604800000)
|
||||||
@@ -189,7 +196,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore(favorites: GalleryList, url: String, onFailure: ((Exception) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List<Int>) -> Unit)? = null) {
|
||||||
if (!URLUtil.isValidUrl(url)) {
|
if (!URLUtil.isValidUrl(url)) {
|
||||||
onFailure?.invoke(IllegalArgumentException())
|
onFailure?.invoke(IllegalArgumentException())
|
||||||
return
|
return
|
||||||
@@ -206,10 +213,12 @@ fun restore(favorites: GalleryList, url: String, onFailure: ((Exception) -> Unit
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
Json.decodeFromString<List<Int>>(response.body().use { it?.string() } ?: "[]").let {
|
kotlin.runCatching {
|
||||||
favorites.addAll(it)
|
Json.decodeFromString<List<Int>>(response.also { if (it.code() != 200) throw IOException() }.body().use { it?.string() } ?: "[]").let {
|
||||||
onSuccess?.invoke(it)
|
favorites.addAll(it)
|
||||||
}
|
onSuccess?.invoke(it)
|
||||||
|
}
|
||||||
|
}.onFailure { onFailure?.invoke(it) }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -224,12 +233,15 @@ private val receiver = object: BroadcastReceiver() {
|
|||||||
ACTION_CANCEL -> {
|
ACTION_CANCEL -> {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
NotificationManagerCompat.from(context).cancel(R.id.notification_id_import)
|
NotificationManagerCompat.from(context).cancel(R.id.notification_id_import)
|
||||||
|
context.unregisterReceiver(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
||||||
|
registerReceiver(receiver, IntentFilter().apply { addAction(receiver.ACTION_CANCEL) })
|
||||||
|
|
||||||
val notificationManager = NotificationManagerCompat.from(this)
|
val notificationManager = NotificationManagerCompat.from(this)
|
||||||
val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
val action = NotificationCompat.Action.Builder(0, getText(android.R.string.cancel),
|
||||||
PendingIntent.getBroadcast(this, R.id.notification_import_cancel_action.normalizeID(), Intent(receiver.ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)
|
PendingIntent.getBroadcast(this, R.id.notification_import_cancel_action.normalizeID(), Intent(receiver.ACTION_CANCEL), PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
@@ -245,55 +257,68 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
|
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
job = CoroutineScope(Dispatchers.IO).launch {
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
val folders = downloadFolder.listFiles { folder ->
|
val downloadFolders = downloadFolder.listFiles { folder ->
|
||||||
(folder as? FileX)?.isDirectory == true && !downloadFolderMap.values.contains(folder.name)
|
folder.isDirectory && !downloadFolderMap.values.contains(folder.name)
|
||||||
|
}?.map {
|
||||||
|
if (it !is FileX)
|
||||||
|
FileX(this@migrate, it)
|
||||||
|
else
|
||||||
|
it
|
||||||
}
|
}
|
||||||
if (folders.isNullOrEmpty()) return@launch
|
|
||||||
folders.forEachIndexed { index, folder ->
|
if (downloadFolders.isNullOrEmpty()) return@launch
|
||||||
|
|
||||||
|
downloadFolders.forEachIndexed { index, folder ->
|
||||||
notification
|
notification
|
||||||
.setContentText(getString(R.string.import_old_galleries_notification_text, index, folders.size))
|
.setContentText(getString(R.string.import_old_galleries_notification_text, index, downloadFolders.size))
|
||||||
.setProgress(index, folders.size, false)
|
.setProgress(index, downloadFolders.size, false)
|
||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
notificationManager.notify(R.id.notification_id_import, notification.build())
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val folder = (folder as? FileX) ?: return@runCatching
|
val metadata = kotlin.runCatching {
|
||||||
|
folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it).jsonObject }
|
||||||
|
}.getOrNull()
|
||||||
|
|
||||||
val metadata = folder.getChild(".metadata").readText()?.let { Json.parseToJsonElement(it).jsonObject } ?: return@runCatching
|
val galleryID = folder.name.toIntOrNull() ?: return@runCatching
|
||||||
|
|
||||||
val galleryBlock: GalleryBlock? =
|
val galleryBlock: GalleryBlock? = kotlin.runCatching {
|
||||||
metadata["galleryBlock"]?.let { Json.decodeFromJsonElement<GalleryBlock>(it) }
|
metadata?.get("galleryBlock")?.let { Json.decodeFromJsonElement<GalleryBlock>(it) }
|
||||||
val reader: Reader? =
|
}.getOrNull() ?: getGalleryBlock(galleryID)
|
||||||
metadata["reader"]?.let { Json.decodeFromJsonElement<Reader>(it) }
|
val reader: Reader? = kotlin.runCatching {
|
||||||
|
metadata?.get("reader")?.let { Json.decodeFromJsonElement<Reader>(it) }
|
||||||
|
}.getOrNull() ?: getReader(galleryID)
|
||||||
|
|
||||||
val galleryID = galleryBlock?.id ?: reader?.galleryInfo?.id ?: folder.name.toIntOrNull() ?: return@runCatching
|
metadata?.get("thumbnail")?.jsonPrimitive?.contentOrNull?.also { thumbnail ->
|
||||||
|
|
||||||
metadata["thumbnail"]?.jsonPrimitive?.contentOrNull.let { thumbnail ->
|
|
||||||
val file = folder.getChild(".thumbnail").also {
|
val file = folder.getChild(".thumbnail").also {
|
||||||
if (!it.exists())
|
if (it.exists())
|
||||||
it.createNewFile()
|
it.delete()
|
||||||
|
it.createNewFile()
|
||||||
}
|
}
|
||||||
|
|
||||||
file.writeBytes(Base64.decode(thumbnail, Base64.DEFAULT))
|
file.writeBytes(Base64.decode(thumbnail, Base64.DEFAULT))
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadFolderMap[galleryID] = folder.name
|
|
||||||
|
|
||||||
val cache = Cache.getInstance(this@migrate, galleryID)
|
|
||||||
|
|
||||||
val list: MutableList<String?> =
|
val list: MutableList<String?> =
|
||||||
MutableList(cache.getReader()!!.galleryInfo.files.size) { null }
|
MutableList(reader!!.galleryInfo.files.size) { null }
|
||||||
|
|
||||||
folder.listFiles { dir ->
|
folder.listFiles { file ->
|
||||||
dir?.nameWithoutExtension?.toIntOrNull() != null
|
file?.nameWithoutExtension?.let {
|
||||||
|
Regex("""\d{5}""").matches(it) && it.toIntOrNull() != null
|
||||||
|
} == true
|
||||||
}?.forEach {
|
}?.forEach {
|
||||||
list[it.nameWithoutExtension.toInt()] = it.name
|
list[it.nameWithoutExtension.toInt()] = it.name
|
||||||
}
|
}
|
||||||
|
|
||||||
cache.setMetadata {
|
folder.getChild(".metadata").also { if (it.exists()) it.delete(); it.createNewFile() }.writeText(
|
||||||
it.galleryBlock = galleryBlock
|
Json.encodeToString(Metadata(galleryBlock, reader, list))
|
||||||
it.reader = reader
|
)
|
||||||
it.imageList = list
|
|
||||||
|
synchronized(Cache) {
|
||||||
|
Cache.delete(galleryID)
|
||||||
}
|
}
|
||||||
|
downloadFolderMap[galleryID] = folder.name
|
||||||
|
|
||||||
|
downloadFolder.getChild(".download").let { if (!it.exists()) it.createNewFile(); it.writeText(Json.encodeToString(downloadFolderMap)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -303,5 +328,9 @@ fun xyz.quaver.pupil.util.downloader.DownloadManager.migrate() {
|
|||||||
.setOngoing(false)
|
.setOngoing(false)
|
||||||
.mActions.clear()
|
.mActions.clear()
|
||||||
notificationManager.notify(R.id.notification_id_import, notification.build())
|
notificationManager.notify(R.id.notification_id_import, notification.build())
|
||||||
|
|
||||||
|
kotlin.runCatching {
|
||||||
|
unregisterReceiver(receiver)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 585 B |
Binary file not shown.
|
Before Width: | Height: | Size: 366 B |
Binary file not shown.
|
Before Width: | Height: | Size: 700 B |
Binary file not shown.
|
Before Width: | Height: | Size: 1.0 KiB |
8
app/src/main/res/drawable/close.xml
Normal file
8
app/src/main/res/drawable/close.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/close.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/delete.xml
Normal file
8
app/src/main/res/drawable/delete.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/delete.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/history.xml
Normal file
8
app/src/main/res/drawable/history.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/history.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M13.5,8H12V13L16.28,15.54L17,14.33L13.5,12.25V8M13,3A9,9 0 0,0 4,12H1L4.96,16.03L9,12H6A7,7 0 0,1 13,5A7,7 0 0,1 20,12A7,7 0 0,1 13,19C11.07,19 9.32,18.21 8.06,16.94L6.64,18.36C8.27,20 10.5,21 13,21A9,9 0 0,0 22,12A9,9 0 0,0 13,3" />
|
||||||
|
</vector>
|
||||||
8
app/src/main/res/drawable/swap_horizontal.xml
Normal file
8
app/src/main/res/drawable/swap_horizontal.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<!-- drawable/swap_horizontal.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="?attr/colorControlNormal" android:pathData="M21,9L17,5V8H10V10H17V13M7,11L3,15L7,19V16H14V14H7V11Z" />
|
||||||
|
</vector>
|
||||||
@@ -63,18 +63,22 @@
|
|||||||
android:text="@string/main_no_result"
|
android:text="@string/main_no_result"
|
||||||
android:visibility="invisible"/>
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
android:id="@+id/main_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingTop="64dp"
|
app:handleHeight="100dp"
|
||||||
android:clipToPadding="false"
|
app:addLastItemPadding="true"
|
||||||
app:fastScrollEnabled="true"
|
app:popupDrawable="@color/transparent">
|
||||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
|
||||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
|
android:id="@+id/main_recyclerview"
|
||||||
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
|
android:layout_width="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="64dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionMenu
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
android:id="@+id/main_fab"
|
android:id="@+id/main_fab"
|
||||||
|
|||||||
@@ -63,18 +63,22 @@
|
|||||||
android:text="@string/main_no_result"
|
android:text="@string/main_no_result"
|
||||||
android:visibility="invisible"/>
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
android:id="@+id/main_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:paddingTop="64dp"
|
app:handleHeight="100dp"
|
||||||
android:clipToPadding="false"
|
app:addLastItemPadding="true"
|
||||||
app:fastScrollEnabled="true"
|
app:popupDrawable="@color/transparent">
|
||||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
|
||||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
|
android:id="@+id/main_recyclerview"
|
||||||
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
|
android:layout_width="match_parent"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="64dp"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<com.github.clans.fab.FloatingActionMenu
|
<com.github.clans.fab.FloatingActionMenu
|
||||||
android:id="@+id/main_fab"
|
android:id="@+id/main_fab"
|
||||||
|
|||||||
@@ -26,16 +26,20 @@
|
|||||||
android:background="@color/dark_gray"
|
android:background="@color/dark_gray"
|
||||||
tools:context=".ui.ReaderActivity">
|
tools:context=".ui.ReaderActivity">
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
|
||||||
android:id="@+id/reader_recyclerview"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:fastScrollEnabled="true"
|
app:handleHeight="100dp"
|
||||||
app:fastScrollHorizontalThumbDrawable="@drawable/thumb_drawable"
|
app:addLastItemPadding="true"
|
||||||
app:fastScrollVerticalThumbDrawable="@drawable/thumb_drawable"
|
app:popupDrawable="@color/transparent">
|
||||||
app:fastScrollHorizontalTrackDrawable="@drawable/track_drawable"
|
|
||||||
app:fastScrollVerticalTrackDrawable="@drawable/track_drawable"
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
android:id="@+id/reader_recyclerview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
</com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -17,61 +17,52 @@
|
|||||||
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
app:layout_constraintHeight_max="2000dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:paddingBottom="8dp"
|
||||||
|
android:background="@drawable/reader_item_boundary">
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
<LinearLayout
|
||||||
android:id="@+id/container"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="0dp"
|
|
||||||
app:layout_constraintHeight_max="2000dp"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
android:background="@drawable/reader_item_boundary">
|
android:gravity="center_horizontal"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
|
android:id="@+id/reader_item_progressbar"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
style="?android:progressBarStyleHorizontal"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
android:indeterminate="false"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
android:progress="0"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
android:max="100"
|
||||||
android:gravity="center_horizontal"
|
android:visibility="visible"/>
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<ProgressBar
|
<TextView
|
||||||
android:id="@+id/reader_item_progressbar"
|
android:id="@+id/reader_index"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
style="?android:progressBarStyleHorizontal"
|
style="@style/TextAppearance.AppCompat.Caption"/>
|
||||||
android:indeterminate="false"
|
|
||||||
android:progress="0"
|
|
||||||
android:max="100"
|
|
||||||
android:visibility="visible"/>
|
|
||||||
|
|
||||||
<TextView
|
</LinearLayout>
|
||||||
android:id="@+id/reader_index"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
style="@style/TextAppearance.AppCompat.Caption"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
|
android:id="@+id/image"
|
||||||
<com.github.chrisbanes.photoview.PhotoView
|
android:adjustViewBounds="true"
|
||||||
android:id="@+id/image"
|
android:scaleType="fitXY"
|
||||||
android:adjustViewBounds="true"
|
android:layout_width="match_parent"
|
||||||
android:scaleType="fitCenter"
|
android:layout_height="wrap_content"
|
||||||
android:layout_width="match_parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
android:layout_height="match_parent"
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
android:paddingBottom="8dp"/>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
32
app/src/main/res/layout/suggestion_count.xml
Normal file
32
app/src/main/res/layout/suggestion_count.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ Pupil, Hitomi.la viewer for Android
|
||||||
|
~ Copyright (C) 2020 tom5079
|
||||||
|
~
|
||||||
|
~ This program is free software: you can redistribute it and/or modify
|
||||||
|
~ it under the terms of the GNU General Public License as published by
|
||||||
|
~ the Free Software Foundation, either version 3 of the License, or
|
||||||
|
~ (at your option) any later version.
|
||||||
|
~
|
||||||
|
~ This program is distributed in the hope that it will be useful,
|
||||||
|
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
~ GNU General Public License for more details.
|
||||||
|
~
|
||||||
|
~ You should have received a copy of the GNU General Public License
|
||||||
|
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/count"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="end|center"
|
||||||
|
android:layout_marginStart="@dimen/search_bar_search_input_left_margin"
|
||||||
|
android:gravity="end|center"
|
||||||
|
android:paddingBottom="4dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:textSize="@dimen/suggestion_body_text_size"
|
||||||
|
tools:text="body"
|
||||||
|
android:layout_marginLeft="@dimen/search_bar_search_input_left_margin" />
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
|
|
||||||
<item android:id="@+id/main_drawer_history"
|
<item android:id="@+id/main_drawer_history"
|
||||||
android:title="@string/main_drawer_history"
|
android:title="@string/main_drawer_history"
|
||||||
android:icon="@drawable/ic_history"/>
|
android:icon="@drawable/history"/>
|
||||||
|
|
||||||
<item android:id="@+id/main_drawer_downloads"
|
<item android:id="@+id/main_drawer_downloads"
|
||||||
android:title="@string/main_drawer_downloads"
|
android:title="@string/main_drawer_downloads"
|
||||||
|
|||||||
@@ -145,4 +145,7 @@
|
|||||||
<string name="settings_invalid_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_name_message">%sに含まれている文字列を対応する変数に置換します\n\n%s</string>
|
||||||
<string name="settings_manage_storage">ストレージ管理</string>
|
<string name="settings_manage_storage">ストレージ管理</string>
|
||||||
|
<string name="settings_oss">オープンソースライセンス</string>
|
||||||
|
<string name="search_show_tags">お気に入りのタグを見る</string>
|
||||||
|
<string name="search_show_histories">履歴を見る</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -145,4 +145,7 @@
|
|||||||
<string name="settings_invalid_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_name_message">지원되는 변수는 %s 입니다\n\n%s</string>
|
||||||
<string name="settings_manage_storage">저장소 관리</string>
|
<string name="settings_manage_storage">저장소 관리</string>
|
||||||
|
<string name="settings_oss">오픈 소스 라이선스</string>
|
||||||
|
<string name="search_show_histories">검색 기록 보기</string>
|
||||||
|
<string name="search_show_tags">즐겨찾기 태그 보기</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -5,4 +5,6 @@
|
|||||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
|
||||||
<dimen name="galleryblock_thumbnail_thin">100dp</dimen>
|
<dimen name="galleryblock_thumbnail_thin">100dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="reader_max_height">2000dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -75,6 +75,8 @@
|
|||||||
|
|
||||||
<string name="search_hint">Search galleries</string>
|
<string name="search_hint">Search galleries</string>
|
||||||
<string name="search_all">Search all galleries</string>
|
<string name="search_all">Search all galleries</string>
|
||||||
|
<string name="search_show_histories">Show histories</string>
|
||||||
|
<string name="search_show_tags">Show favorite tags</string>
|
||||||
|
|
||||||
<string name="gallery_details">Details</string>
|
<string name="gallery_details">Details</string>
|
||||||
<string name="gallery_thumbnails">Thumbnails</string>
|
<string name="gallery_thumbnails">Thumbnails</string>
|
||||||
@@ -172,6 +174,7 @@
|
|||||||
<string name="settings_import_old_galleries">Import old galleries</string>
|
<string name="settings_import_old_galleries">Import old galleries</string>
|
||||||
<string name="settings_user_id">User ID</string>
|
<string name="settings_user_id">User ID</string>
|
||||||
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
|
<string name="settings_user_id_toast">User ID is copied to clipboard</string>
|
||||||
|
<string name="settings_oss">Open Source Notice</string>
|
||||||
|
|
||||||
<!-- MANAGE FAVORITES -->
|
<!-- MANAGE FAVORITES -->
|
||||||
|
|
||||||
|
|||||||
@@ -104,6 +104,10 @@
|
|||||||
app:key="user_id"
|
app:key="user_id"
|
||||||
app:title="@string/settings_user_id"/>
|
app:title="@string/settings_user_id"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="oss"
|
||||||
|
app:title="@string/settings_oss"/>
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
@@ -26,14 +26,21 @@ package xyz.quaver.pupil
|
|||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import kotlinx.serialization.*
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.lang.reflect.ParameterizedType
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KType
|
||||||
|
import kotlin.reflect.typeOf
|
||||||
|
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
|
val a = mutableSetOf<Int>()
|
||||||
|
|
||||||
|
print(a::class.java.methods.firstOrNull { it.name == "add" }?.genericParameterTypes?.firstOrNull() as? ParameterizedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ buildscript {
|
|||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.1'
|
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.2.1'
|
||||||
classpath 'com.google.firebase:perf-plugin:1.3.1'
|
classpath 'com.google.firebase:perf-plugin:1.3.1'
|
||||||
|
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.2'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +23,6 @@ allprojects {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
jcenter()
|
||||||
mavenLocal()
|
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
maven { url 'https://guardian.github.com/maven/repo-releases' }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user