diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt index b332fba7..b021026c 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -20,26 +20,10 @@ package xyz.quaver.pupil -import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule -import kotlinx.coroutines.runBlocking import org.junit.Test 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. @@ -54,77 +38,4 @@ class ExampleInstrumentedTest { // Context of the app under test. 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") - } } \ No newline at end of file diff --git a/app/src/main/java/com/arlib/floatingsearchview/FloatingSearchViewDayNight.kt b/app/src/main/java/com/arlib/floatingsearchview/FloatingSearchViewDayNight.kt index 3fe1fb31..c9ee7326 100644 --- a/app/src/main/java/com/arlib/floatingsearchview/FloatingSearchViewDayNight.kt +++ b/app/src/main/java/com/arlib/floatingsearchview/FloatingSearchViewDayNight.kt @@ -19,13 +19,199 @@ package com.arlib.floatingsearchview import android.content.Context +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.graphics.drawable.Animatable import android.os.Parcelable +import android.text.Editable +import android.text.TextWatcher 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( - context: Context, - attrs: AttributeSet? = null) - : FloatingSearchView(context, attrs) { +class FloatingSearchViewDayNight @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : + FloatingSearchView(context, attrs), + FloatingSearchView.OnSearchListener, + SearchSuggestionsAdapter.OnBindSuggestionCallback, + TextWatcher +{ + + private val searchInputView = findViewById(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(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(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(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 override fun onSaveInstanceState(): Parcelable? { diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 1ad37c1d..ee0e4335 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -41,6 +41,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import xyz.quaver.io.FileX +import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.util.* import xyz.quaver.setClient import java.io.File @@ -50,9 +51,13 @@ import kotlin.reflect.KClass typealias PupilInterceptor = (Interceptor.Chain) -> Response -lateinit var histories: GalleryList +lateinit var histories: SavedSet private set -lateinit var favorites: GalleryList +lateinit var favorites: SavedSet + private set +lateinit var favoriteTags: SavedSet + private set +lateinit var searchHistory: SavedSet private set val interceptors = mutableMapOf, PupilInterceptor>() @@ -110,8 +115,10 @@ class Pupil : Application() { Preferences.remove("download_folder") } - histories = GalleryList(File(ContextCompat.getDataDir(this), "histories.json")) - favorites = GalleryList(File(ContextCompat.getDataDir(this), "favorites.json")) + histories = SavedSet(File(ContextCompat.getDataDir(this), "histories.json"), 0) + 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"]) { CoroutineScope(Dispatchers.IO).launch { diff --git a/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt b/app/src/main/java/xyz/quaver/pupil/types/Suggestions.kt similarity index 70% rename from app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt rename to app/src/main/java/xyz/quaver/pupil/types/Suggestions.kt index f3795283..e49391e4 100644 --- a/app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt +++ b/app/src/main/java/xyz/quaver/pupil/types/Suggestions.kt @@ -29,4 +29,25 @@ data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String override fun getBody(): String { 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 } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt index b5810214..2325fca8 100644 --- a/app/src/main/java/xyz/quaver/pupil/types/Tags.kt +++ b/app/src/main/java/xyz/quaver/pupil/types/Tags.kt @@ -24,7 +24,7 @@ import kotlinx.serialization.Serializable data class Tag(val area: String?, val tag: String, val isNegative: Boolean = false) { companion object { fun parse(tag: String) : Tag { - if (tag.first() == '-') { + if (tag.firstOrNull() == '-') { tag.substring(1).split(Regex(":"), 2).let { return when(it.size) { 2 -> Tag(it[0], it[1], true) @@ -62,9 +62,7 @@ data class Tag(val area: String?, val tag: String, val isNegative: Boolean = fal return false } - override fun hashCode(): Int { - return super.hashCode() - } + override fun hashCode() = toString().hashCode() } class Tags(val tags: MutableSet = mutableSetOf()) : MutableSet by tags { @@ -110,7 +108,4 @@ class Tags(val tags: MutableSet = mutableSetOf()) : MutableSet by tags override fun toString(): String { return tags.joinToString(" ") { it.toString() } } - - - } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt new file mode 100644 index 00000000..5963073c --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt @@ -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 . + */ + +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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt index 90948629..e805bb0f 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt @@ -42,6 +42,7 @@ import xyz.quaver.pupil.util.Preferences class LockActivity : AppCompatActivity() { private lateinit var lockManager: LockManager + private var lastUnlocked = 0L private var mode: String? = null private val patternLockFragment = PatternLockFragment().apply { @@ -52,6 +53,7 @@ class LockActivity : AppCompatActivity() { val result = lockManager.check(it) if (result == true) { + lastUnlocked = System.currentTimeMillis() setResult(Activity.RESULT_OK) finish() } else @@ -86,6 +88,7 @@ class LockActivity : AppCompatActivity() { val result = lockManager.check(it) if (result == true) { + lastUnlocked = System.currentTimeMillis() setResult(Activity.RESULT_OK) finish() } else { @@ -157,6 +160,7 @@ class LockActivity : AppCompatActivity() { override fun onAuthenticationSucceeded( result: BiometricPrompt.AuthenticationResult) { super.onAuthenticationSucceeded(result) + lastUnlocked = System.currentTimeMillis() setResult(RESULT_OK) finish() return @@ -185,6 +189,7 @@ class LockActivity : AppCompatActivity() { } mode = intent.getStringExtra("mode") + val force = intent.getBooleanExtra("force", false) when(mode) { null -> { @@ -194,6 +199,13 @@ class LockActivity : AppCompatActivity() { return } + if (System.currentTimeMillis() - lastUnlocked < 5*60*1000 && !force) { + lastUnlocked = System.currentTimeMillis() + setResult(RESULT_OK) + finish() + return + } + if ( Preferences["lock_fingerprint"] && BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt index 30dff78f..a891eadf 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -19,65 +19,49 @@ package xyz.quaver.pupil.ui import android.annotation.SuppressLint -import android.app.Activity import android.content.Intent -import android.graphics.drawable.Animatable import android.net.Uri import android.os.Bundle -import android.text.* -import android.text.style.AlignmentSpan -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.text.InputType +import android.view.* import android.widget.* import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView -import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import androidx.core.view.GravityCompat -import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.FloatingSearchViewDayNight import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.util.view.SearchInputView import com.bumptech.glide.Glide import com.google.android.material.appbar.AppBarLayout +import com.google.android.material.navigation.NavigationView import com.google.android.material.snackbar.Snackbar import com.google.firebase.crashlytics.FirebaseCrashlytics import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main_content.* 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.getGalleryIDsFromNozomi import xyz.quaver.hitomi.getSuggestionsForQuery -import xyz.quaver.pupil.R +import xyz.quaver.pupil.* 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.types.TagSuggestion -import xyz.quaver.pupil.types.Tags +import xyz.quaver.pupil.types.* import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment import xyz.quaver.pupil.ui.dialog.GalleryDialog import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.downloader.Cache 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.ceil import kotlin.math.min import kotlin.math.roundToInt -class MainActivity : AppCompatActivity() { +class MainActivity : + BaseActivity(), + FloatingSearchView.OnMenuItemClickListener, + NavigationView.OnNavigationItemSelectedListener +{ enum class Mode { SEARCH, @@ -115,26 +99,9 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { 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) { intent.dataString?.let { url -> - restore(favorites, url, + restore(url, onFailure = { Snackbar.make(this.main_recyclerview, R.string.settings_backup_failed, Snackbar.LENGTH_LONG).show() }, onSuccess = { @@ -176,17 +143,6 @@ class MainActivity : AppCompatActivity() { (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 { val perPage = Preferences["per_page", "25"].toInt() val maxPage = ceil(totalItems / perPage.toDouble()).roundToInt() @@ -234,10 +190,6 @@ class MainActivity : AppCompatActivity() { loadBlocks() } } - R.id.request_lock.normalizeID() -> { - if (resultCode != Activity.RESULT_OK) - finish() - } else -> super.onActivityResult(requestCode, resultCode, data) } } @@ -261,71 +213,7 @@ class MainActivity : AppCompatActivity() { ) //NavigationView - main_nav_view.setNavigationItemSelectedListener { - 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 - } + main_nav_view.setNavigationItemSelectedListener(this) with(main_fab_cancel) { setImageResource(R.drawable.cancel) @@ -720,36 +608,24 @@ class MainActivity : AppCompatActivity() { } } + private var isFavorite = false + private val defaultSuggestions: List + 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 fun setupSearchBar() { - val searchInputView = findViewById(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) { - val favoritesFile = File(ContextCompat.getDataDir(context), "favorites_tags.json") - - if (!favoritesFile.exists()) { - favoritesFile.createNewFile() - favoritesFile.writeText("[]") - } - setOnLeftMenuClickListener(object: FloatingSearchView.OnLeftMenuClickListener { override fun onMenuOpened() { (this@MainActivity.main_recyclerview.adapter as GalleryBlockAdapter).closeAllItems() @@ -760,62 +636,30 @@ class MainActivity : AppCompatActivity() { } }) - setOnMenuItemClickListener { - when(it.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 - 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() - } - } - } + onHistoryDeleteClickedListener = { + searchHistory.remove(it) + swapSuggestions(defaultSuggestions) } + onFavoriteHistorySwitchClickListener = { + isFavorite = !isFavorite + swapSuggestions(defaultSuggestions) + } + + setOnMenuItemClickListener(this@MainActivity) setOnQueryChangeListener { _, query -> this@MainActivity.query = query suggestionJob?.cancel() - clearSuggestions() - if (query.isEmpty() or query.endsWith(' ')) { - swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map { - TagSuggestion(it.tag, -1, "", it.area ?: "tag") - }) + swapSuggestions(defaultSuggestions) return@setOnQueryChangeListener } + swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString()))) + val currentQuery = query.split(" ").last().replace('_', ' ') suggestionJob = CoroutineScope(Dispatchers.IO).launch { @@ -825,113 +669,22 @@ class MainActivity : AppCompatActivity() { suggestions.filter { val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}" - Tags(Json.decodeFromString(favoritesFile.readText())).contains(tag) + favoriteTags.contains(Tag.parse(tag)) }.reversed().forEach { suggestions.remove(it) suggestions.add(0, it) } 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(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 { override fun onFocus() { if (query.isEmpty() or query.endsWith(' ')) - swapSuggestions(Tags(Json.decodeFromString(favoritesFile.readText())).map { - TagSuggestion(it.tag, -1, "", it.area ?: "tag") - }) + swapSuggestions(defaultSuggestions) } 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() { galleryIDs?.cancel() loadingJob?.cancel() @@ -974,6 +834,9 @@ class MainActivity : AppCompatActivity() { private fun fetchGalleries(query: String, sortMode: SortMode) { val defaultQuery: String = Preferences["default_query"] + if (query.isNotBlank()) + searchHistory.add(query) + if (query != queryStack.lastOrNull()) { queryStack.remove(query) queryStack.add(query) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt index c82112d5..87bf4ff5 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -28,7 +28,6 @@ import android.os.IBinder import android.view.* import android.widget.Toast import androidx.appcompat.app.AlertDialog -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager @@ -57,7 +56,7 @@ import java.util.* import kotlin.concurrent.schedule import kotlin.concurrent.timer -class ReaderActivity : AppCompatActivity() { +class ReaderActivity : BaseActivity() { private var galleryID = 0 private var currentPage = 0 @@ -101,10 +100,6 @@ class ReaderActivity : AppCompatActivity() { title = getString(R.string.reader_loading) supportActionBar?.setDisplayHomeAsUpEnabled(false) - window.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE) - handleIntent(intent) cache = Cache.getInstance(this, galleryID) FirebaseCrashlytics.getInstance().setCustomKey("GalleryID", galleryID) @@ -113,6 +108,7 @@ class ReaderActivity : AppCompatActivity() { onBackPressed() return } + if (Preferences["cache_disable"]) { reader_download_progressbar.visibility = View.GONE 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 { menuInflater.inflate(R.menu.reader, menu) diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt index 9ee58537..7e797ac5 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -37,15 +37,10 @@ import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.normalizeID import java.nio.charset.Charset -class SettingsActivity : AppCompatActivity() { +class SettingsActivity : BaseActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - window.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE) - setContentView(R.layout.settings_activity) supportFragmentManager .beginTransaction() @@ -54,16 +49,6 @@ class SettingsActivity : AppCompatActivity() { 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 { when (item.itemId) { android.R.id.home -> onBackPressed() @@ -72,48 +57,6 @@ class SettingsActivity : AppCompatActivity() { 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>(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") override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { when (requestCode) { diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt index 8dc8b271..e190e22c 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/ManageFavoritesFragment.kt @@ -82,7 +82,7 @@ class ManageFavoritesFragment : PreferenceFragmentCompat() { .setTitle(R.string.settings_restore_title) .setView(editText) .setPositiveButton(android.R.string.ok) { _, _ -> - restore(favorites, editText.text.toString(), + restore(editText.text.toString(), onFailure = onFailure@{ val view = view ?: return@onFailure Snackbar.make(view, R.string.settings_restore_failed, Snackbar.LENGTH_LONG).show() diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt index cdfcc7e3..3514ff42 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt @@ -18,6 +18,7 @@ package xyz.quaver.pupil.ui.fragment +import android.app.Activity import android.content.* import android.os.Bundle import android.widget.Toast @@ -27,14 +28,19 @@ import androidx.preference.PreferenceCategory import androidx.preference.PreferenceFragmentCompat 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.util.getChild import xyz.quaver.pupil.R +import xyz.quaver.pupil.favorites import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.SettingsActivity import xyz.quaver.pupil.ui.dialog.* import xyz.quaver.pupil.util.* import xyz.quaver.pupil.util.downloader.DownloadManager +import java.nio.charset.Charset class SettingsFragment : PreferenceFragmentCompat(), @@ -69,7 +75,7 @@ class SettingsFragment : checkUpdate(activity as SettingsActivity, true) } "download_folder" -> { - DownloadLocationDialogFragment().show(requireActivity().supportFragmentManager, "Download Location Dialog") + DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog") } "default_query" -> { DefaultQueryDialog(requireContext()).apply { @@ -80,8 +86,10 @@ class SettingsFragment : }.show() } "app_lock" -> { - val intent = Intent(requireContext(), LockActivity::class.java) - activity?.startActivityForResult(intent, R.id.request_lock.normalizeID()) + val intent = Intent(requireContext(), LockActivity::class.java).apply { + putExtra("force", true) + } + startActivityForResult(intent, R.id.request_lock.normalizeID()) } "mirrors" -> { MirrorDialog(requireContext()) @@ -259,4 +267,19 @@ class SettingsFragment : } } } + + 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/util/GalleryList.kt b/app/src/main/java/xyz/quaver/pupil/util/SavedSet.kt similarity index 57% rename from app/src/main/java/xyz/quaver/pupil/util/GalleryList.kt rename to app/src/main/java/xyz/quaver/pupil/util/SavedSet.kt index 62ec45b5..748f67b4 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/GalleryList.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/SavedSet.kt @@ -1,6 +1,6 @@ /* * 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 * it under the terms of the GNU General Public License as published by @@ -18,12 +18,17 @@ package xyz.quaver.pupil.util -import kotlinx.serialization.decodeFromString -import kotlinx.serialization.encodeToString +import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json import java.io.File -class GalleryList(private val file: File, private val list: MutableSet = mutableSetOf()) : MutableSet by list { +class SavedSet (private val file: File, private val any: T, private val set: MutableSet = mutableSetOf()) : MutableSet by set { + + @Suppress("UNCHECKED_CAST") + @OptIn(ExperimentalSerializationApi::class) + val serializer: KSerializer> + get() = ListSerializer(serializer(any::class.java) as KSerializer) init { if (!file.exists()) { @@ -35,47 +40,48 @@ class GalleryList(private val file: File, private val list: MutableSet = mu fun load() { synchronized(this) { - list.clear() + set.clear() kotlin.runCatching { - Json.decodeFromString>(file.bufferedReader().use { it.readText() }) + Json.decodeFromString(serializer, file.readText()) }.onSuccess { - list.addAll(it) + set.addAll(it) } } } + @OptIn(ExperimentalSerializationApi::class) fun save() { 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() - return list.add(element).also { + return set.add(element).also { save() } } - override fun addAll(elements: Collection): Boolean { + override fun addAll(elements: Collection): Boolean { load() - return list.addAll(elements).also { + return set.addAll(elements).also { save() } } - override fun remove(element: Int): Boolean { + override fun remove(element: T): Boolean { load() - return list.remove(element).also { + return set.remove(element).also { save() } } override fun clear() { - list.clear() + set.clear() save() } diff --git a/app/src/main/java/xyz/quaver/pupil/util/update.kt b/app/src/main/java/xyz/quaver/pupil/util/update.kt index 36806214..e13eefd4 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/update.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/update.kt @@ -56,6 +56,7 @@ import xyz.quaver.io.util.* import xyz.quaver.pupil.BuildConfig import xyz.quaver.pupil.R import xyz.quaver.pupil.client +import xyz.quaver.pupil.favorites import xyz.quaver.pupil.services.DownloadService import xyz.quaver.pupil.util.downloader.Cache 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) -> Unit)? = null) { +fun restore(url: String, onFailure: ((Throwable) -> Unit)? = null, onSuccess: ((List) -> Unit)? = null) { if (!URLUtil.isValidUrl(url)) { onFailure?.invoke(IllegalArgumentException()) return diff --git a/app/src/main/res/drawable-hdpi/ic_history.png b/app/src/main/res/drawable-hdpi/ic_history.png deleted file mode 100644 index 8610919f..00000000 Binary files a/app/src/main/res/drawable-hdpi/ic_history.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/ic_history.png b/app/src/main/res/drawable-mdpi/ic_history.png deleted file mode 100644 index e64059c8..00000000 Binary files a/app/src/main/res/drawable-mdpi/ic_history.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/ic_history.png b/app/src/main/res/drawable-xhdpi/ic_history.png deleted file mode 100644 index 4a9622d2..00000000 Binary files a/app/src/main/res/drawable-xhdpi/ic_history.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_history.png b/app/src/main/res/drawable-xxhdpi/ic_history.png deleted file mode 100644 index c22644eb..00000000 Binary files a/app/src/main/res/drawable-xxhdpi/ic_history.png and /dev/null differ diff --git a/app/src/main/res/drawable/close.xml b/app/src/main/res/drawable/close.xml new file mode 100644 index 00000000..a1e3d0a6 --- /dev/null +++ b/app/src/main/res/drawable/close.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete.xml b/app/src/main/res/drawable/delete.xml new file mode 100644 index 00000000..ef44b59c --- /dev/null +++ b/app/src/main/res/drawable/delete.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/history.xml b/app/src/main/res/drawable/history.xml new file mode 100644 index 00000000..12f3d5a9 --- /dev/null +++ b/app/src/main/res/drawable/history.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/swap_horizontal.xml b/app/src/main/res/drawable/swap_horizontal.xml new file mode 100644 index 00000000..e962fbf1 --- /dev/null +++ b/app/src/main/res/drawable/swap_horizontal.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/suggestion_count.xml b/app/src/main/res/layout/suggestion_count.xml new file mode 100644 index 00000000..6957609a --- /dev/null +++ b/app/src/main/res/layout/suggestion_count.xml @@ -0,0 +1,32 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index dbe01867..2d45b181 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -27,7 +27,7 @@ + android:icon="@drawable/history"/> %sに含まれている文字列を対応する変数に置換します\n\n%s ストレージ管理 オープンソースライセンス + お気に入りのタグを見る + 履歴を見る \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3d1424d2..f18284ed 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -146,4 +146,6 @@ 지원되는 변수는 %s 입니다\n\n%s 저장소 관리 오픈 소스 라이선스 + 검색 기록 보기 + 즐겨찾기 태그 보기 \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d3b5907c..7695d3e4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -75,6 +75,8 @@ Search galleries Search all galleries + Show histories + Show favorite tags Details Thumbnails diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt index d2dc194e..16349253 100644 --- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt +++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt @@ -26,14 +26,21 @@ package xyz.quaver.pupil * See [testing documentation](http://d.android.com/tools/testing). */ +import kotlinx.serialization.* import kotlinx.serialization.json.Json import org.junit.Test +import java.lang.reflect.ParameterizedType +import kotlin.reflect.KClass +import kotlin.reflect.KType +import kotlin.reflect.typeOf class ExampleUnitTest { @Test fun test() { - + val a = mutableSetOf() + + print(a::class.java.methods.firstOrNull { it.name == "add" }?.genericParameterTypes?.firstOrNull() as? ParameterizedType) } }