Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
297ce506b1 | ||
|
|
18c6954be3 | ||
|
|
cea3fb1e65 | ||
|
|
7f274fd238 | ||
|
|
439a8e93ec | ||
|
|
83801feee9 | ||
|
|
8a6860c96e | ||
|
|
5c959f2987 | ||
|
|
4e4397287a |
@@ -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")
|
||||||
@@ -20,7 +21,7 @@ android {
|
|||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 57
|
versionCode 57
|
||||||
versionName "5.0-beta6"
|
versionName "5.0-beta8"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
@@ -99,7 +102,7 @@ dependencies {
|
|||||||
implementation ("xyz.quaver:libpupil:1.5") {
|
implementation ("xyz.quaver:libpupil:1.5") {
|
||||||
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"
|
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/recyclerviewfastscroller-release.aar
Normal file
BIN
app/libs/recyclerviewfastscroller-release.aar
Normal file
Binary file not shown.
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
@@ -21,7 +21,6 @@
|
|||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
|
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-dontoptimize
|
|
||||||
|
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||||
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
-keep class * extends com.bumptech.glide.module.AppGlideModule {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"filters": [],
|
"filters": [],
|
||||||
"properties": [],
|
"properties": [],
|
||||||
"versionCode": 57,
|
"versionCode": 57,
|
||||||
"versionName": "5.0-beta6",
|
"versionName": "5.0-beta8",
|
||||||
"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? {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ 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.setClient
|
import xyz.quaver.setClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -50,9 +51,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>()
|
||||||
@@ -110,8 +115,10 @@ class Pupil : Application() {
|
|||||||
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,7 +26,6 @@ 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
|
||||||
@@ -43,7 +39,6 @@ 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
|
||||||
|
|||||||
@@ -389,8 +389,6 @@ class DownloadService : Service() {
|
|||||||
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it, startId) }
|
COMMAND_DELETE -> intent.getIntExtra(KEY_ID, -1).let { if (it > 0) delete(it, startId) }
|
||||||
}
|
}
|
||||||
|
|
||||||
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
|
||||||
|
|
||||||
return START_NOT_STICKY
|
return START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,6 +400,7 @@ class DownloadService : Service() {
|
|||||||
override fun onBind(p0: Intent?) = binder
|
override fun onBind(p0: Intent?) = binder
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
|
startForeground(R.id.downloader_notification_id, serviceNotification.build())
|
||||||
interceptors[Tag::class] = interceptor
|
interceptors[Tag::class] = interceptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -42,6 +42,7 @@ import xyz.quaver.pupil.util.Preferences
|
|||||||
class LockActivity : AppCompatActivity() {
|
class LockActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private lateinit var lockManager: LockManager
|
private lateinit var lockManager: LockManager
|
||||||
|
private var lastUnlocked = 0L
|
||||||
private var mode: String? = null
|
private var mode: String? = null
|
||||||
|
|
||||||
private val patternLockFragment = PatternLockFragment().apply {
|
private val patternLockFragment = PatternLockFragment().apply {
|
||||||
@@ -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)
|
||||||
@@ -1039,7 +902,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -57,7 +56,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 +100,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 +108,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 +167,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,16 +49,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
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 onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
android.R.id.home -> onBackPressed()
|
android.R.id.home -> onBackPressed()
|
||||||
@@ -76,48 +57,6 @@ class SettingsActivity : AppCompatActivity() {
|
|||||||
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) {
|
||||||
|
|||||||
@@ -82,7 +82,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()
|
||||||
|
|||||||
@@ -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,47 +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()
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
Json.decodeFromString<List<Int>>(file.bufferedReader().use { it.readText() })
|
Json.decodeFromString(serializer, file.readText())
|
||||||
}.onSuccess {
|
}.onSuccess {
|
||||||
list.addAll(it)
|
set.addAll(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
fun save() {
|
fun save() {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
file.writeText(Json.encodeToString(list.toList()))
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,10 +34,7 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class DownloadManager private constructor(context: Context) : ContextWrapper(con
|
|||||||
|
|
||||||
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").let { if (!it.exists()) it.createNewFile() }
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ 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 xyz.quaver.pupil.util.downloader.Metadata
|
||||||
@@ -195,7 +196,7 @@ fun checkUpdate(context: Context, force: Boolean = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restore(favorites: GalleryList, url: String, onFailure: ((Throwable) -> 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
|
||||||
|
|||||||
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"
|
||||||
|
|||||||
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>
|
||||||
@@ -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