Suggestion

This commit is contained in:
tom5079
2020-12-02 09:57:16 +09:00
parent 26c5e07f04
commit 730a3baedc
17 changed files with 227 additions and 241 deletions

View File

@@ -44,8 +44,10 @@ android {
} }
buildTypes { buildTypes {
debug { debug {
minifyEnabled true minifyEnabled false
shrinkResources true shrinkResources false
multiDexEnabled true
debuggable true debuggable true
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
@@ -128,7 +130,7 @@ dependencies {
implementation "xyz.quaver:libpupil:1.9.7" implementation "xyz.quaver:libpupil:1.9.7"
implementation "xyz.quaver:documentfilex:0.4-alpha02" implementation "xyz.quaver:documentfilex:0.4-alpha02"
implementation "xyz.quaver:floatingsearchview:1.0.9" implementation "xyz.quaver:floatingsearchview:1.1.1"
testImplementation "junit:junit:4.13.1" testImplementation "junit:junit:4.13.1"
androidTestImplementation "androidx.test.ext:junit:1.1.2" androidTestImplementation "androidx.test.ext:junit:1.1.2"

View File

@@ -20,18 +20,18 @@ package xyz.quaver.pupil.adapters
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.graphics.drawable.DrawableCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.databinding.SourceSelectDialogItemBinding import xyz.quaver.pupil.databinding.SourceSelectDialogItemBinding
import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.sourceIcons import xyz.quaver.pupil.sources.sourceIcons
class SourceAdapter(private val sources: List<Source<*>>) : RecyclerView.Adapter<SourceAdapter.ViewHolder>() { class SourceAdapter(private val sources: List<Source<*, SearchSuggestion>>) : RecyclerView.Adapter<SourceAdapter.ViewHolder>() {
var onSourceSelectedListener: ((Source<*>) -> Unit)? = null var onSourceSelectedListener: ((Source<*, SearchSuggestion>) -> Unit)? = null
inner class ViewHolder(private val binding: SourceSelectDialogItemBinding) : RecyclerView.ViewHolder(binding.root) { inner class ViewHolder(private val binding: SourceSelectDialogItemBinding) : RecyclerView.ViewHolder(binding.root) {
lateinit var source: Source<*> lateinit var source: Source<*, SearchSuggestion>
init { init {
binding.go.setOnClickListener { binding.go.setOnClickListener {
@@ -39,7 +39,7 @@ class SourceAdapter(private val sources: List<Source<*>>) : RecyclerView.Adapter
} }
} }
fun bind(source: Source<*>) { fun bind(source: Source<*, SearchSuggestion>) {
this.source = source this.source = source
binding.icon.setImageDrawable(sourceIcons[source.name]) binding.icon.setImageDrawable(sourceIcons[source.name])

View File

@@ -22,9 +22,10 @@ import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.parcelize.Parcelize
import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.hitomi.Hitomi
import xyz.quaver.pupil.sources.hitomi.Hiyobi
data class SearchResult( data class SearchResult(
val id: String, val id: String,
@@ -56,15 +57,24 @@ data class SearchResult(
enum class DefaultSortMode { enum class DefaultSortMode {
DEFAULT DEFAULT
} }
interface Source<Query_SortMode : Enum<Query_SortMode>> {
val name: String
val iconResID: Int
val availableSortMode: Array<Query_SortMode>
suspend fun query(query: String, range: IntRange, sortMode: Enum<*>) : Pair<Channel<SearchResult>, Int> @Parcelize
class DefaultSearchSuggestion(override val body: String) : SearchSuggestion
abstract class Source<Query_SortMode: Enum<Query_SortMode>, Suggestion: SearchSuggestion> {
abstract val name: String
abstract val iconResID: Int
abstract val availableSortMode: Array<Query_SortMode>
abstract suspend fun search(query: String, range: IntRange, sortMode: Enum<*>) : Pair<Channel<SearchResult>, Int>
abstract suspend fun suggestion(query: String) : List<Suggestion>
open fun onSuggestionBind(binding: SearchSuggestionItemBinding, item: Suggestion) {
binding.leftIcon.setImageResource(R.drawable.tag)
}
} }
val sources = mutableMapOf<String, Source<*>>() val sources = mutableMapOf<String, Source<*, SearchSuggestion>>()
val sourceIcons = mutableMapOf<String, Drawable?>() val sourceIcons = mutableMapOf<String, Drawable?>()
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -74,7 +84,7 @@ fun initSources(context: Context) {
Hitomi(), Hitomi(),
Hiyobi() Hiyobi()
).forEach { ).forEach {
sources[it.name] = it sources[it.name] = it as Source<*, SearchSuggestion>
sourceIcons[it.name] = ContextCompat.getDrawable(context, it.iconResID) sourceIcons[it.name] = ContextCompat.getDrawable(context, it.iconResID)
} }
} }

View File

@@ -16,26 +16,45 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package xyz.quaver.pupil.sources.hitomi package xyz.quaver.pupil.sources
import android.view.LayoutInflater
import android.widget.TextView
import androidx.core.content.ContextCompat
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hitomi.* import xyz.quaver.hitomi.*
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.SearchResult
import xyz.quaver.pupil.sources.SearchResult.ExtraType import xyz.quaver.pupil.sources.SearchResult.ExtraType
import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.util.translations
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
class Hitomi : Source<Hitomi.SortMode> { class Hitomi : Source<Hitomi.SortMode, Hitomi.TagSuggestion>() {
enum class SortMode { enum class SortMode {
NEWEST, NEWEST,
POPULAR POPULAR
} }
@Parcelize
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) :
SearchSuggestion {
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
@IgnoredOnParcel
override val body =
if (translations[s] != null)
"${translations[s]} ($s)"
else
s
}
override val name: String = "hitomi.la" override val name: String = "hitomi.la"
override val iconResID: Int = R.drawable.hitomi override val iconResID: Int = R.drawable.hitomi
override val availableSortMode: Array<SortMode> = SortMode.values() override val availableSortMode: Array<SortMode> = SortMode.values()
@@ -44,7 +63,7 @@ class Hitomi : Source<Hitomi.SortMode> {
var cachedSortMode: SortMode? = null var cachedSortMode: SortMode? = null
val cache = mutableListOf<Int>() val cache = mutableListOf<Int>()
override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<SearchResult>, Int> { override suspend fun search(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<SearchResult>, Int> {
if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) { if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) {
cachedQuery = null cachedQuery = null
cache.clear() cache.clear()
@@ -79,6 +98,44 @@ class Hitomi : Source<Hitomi.SortMode> {
return Pair(channel, cache.size) return Pair(channel, cache.size)
} }
override suspend fun suggestion(query: String) : List<TagSuggestion> {
return getSuggestionsForQuery(query.takeLastWhile { !it.isWhitespace() }).map {
TagSuggestion(it)
}
}
override fun onSuggestionBind(binding: SearchSuggestionItemBinding, item: TagSuggestion) {
binding.leftIcon.setImageResource(
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
}
)
if (item.t > 0) {
with (binding.root) {
val count = findViewById<TextView>(R.id.count)
if (count == null)
addView(
LayoutInflater.from(context).inflate(R.layout.suggestion_count, binding.root, false)
.apply {
this as TextView
text = item.t.toString()
}, 2
)
else
count.text = item.t.toString()
}
}
}
companion object { companion object {
val languageMap = mapOf( val languageMap = mapOf(
"indonesian" to "Bahasa Indonesia", "indonesian" to "Bahasa Indonesia",

View File

@@ -16,27 +16,30 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
package xyz.quaver.pupil.sources.hitomi package xyz.quaver.pupil.sources
import kotlinx.coroutines.CoroutineScope import androidx.core.content.ContextCompat
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch import kotlinx.serialization.decodeFromString
import xyz.quaver.hitomi.galleryblockdir import kotlinx.serialization.json.Json
import okhttp3.Request
import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hiyobi.* import xyz.quaver.hiyobi.*
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.sources.DefaultSortMode import xyz.quaver.pupil.client
import xyz.quaver.pupil.sources.SearchResult
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize
import java.io.IOException
import java.util.*
class Hiyobi : Source<DefaultSortMode> { class Hiyobi : Source<DefaultSortMode, DefaultSearchSuggestion>() {
override val name: String = "hiyobi.me" override val name: String = "hiyobi.me"
override val iconResID: Int = R.drawable.ic_hiyobi override val iconResID: Int = R.drawable.ic_hiyobi
override val availableSortMode: Array<DefaultSortMode> = DefaultSortMode.values() override val availableSortMode: Array<DefaultSortMode> = DefaultSortMode.values()
override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<SearchResult>, Int> { override suspend fun search(query: String, range: IntRange, sortMode: Enum<*>): Pair<Channel<SearchResult>, Int> {
val channel = Channel<SearchResult>() val channel = Channel<SearchResult>()
val (results, total) = if (query.isEmpty()) val (results, total) = if (query.isEmpty())
@@ -55,7 +58,58 @@ class Hiyobi : Source<DefaultSortMode> {
return Pair(channel, total) return Pair(channel, total)
} }
override suspend fun suggestion(query: String): List<DefaultSearchSuggestion> {
val result = mutableSetOf<String>()
for (tag in allTags.await()) {
if (result.size >= 10)
break
val lowQuery = query.toLowerCase(Locale.ROOT)
if (tag.contains(lowQuery, true))
result.add(tag)
}
return result.map { DefaultSearchSuggestion(it) }
}
override fun onSuggestionBind(binding: SearchSuggestionItemBinding, item: DefaultSearchSuggestion) {
val split = item.body.split(':', limit = 2)
if (split.size != 2)
return
binding.leftIcon.setImageResource(
when(split.first()) {
"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
}
)
binding.body.text = split.last()
}
companion object { companion object {
private fun downloadAllTags(): Deferred<List<String>> = CoroutineScope(Dispatchers.IO).async {
Json.decodeFromString(kotlin.runCatching {
client.newCall(Request.Builder().url("https://api.hiyobi.me/auto.json").build()).execute().also { if (it.code() != 200) throw IOException() }.body()?.use { it.string() }
}.getOrNull() ?: "[]")
}
private var _allTags: Deferred<List<String>>? = null
val allTags: Deferred<List<String>>
get() = if (_allTags == null || (_allTags!!.isCompleted && runBlocking { _allTags!!.await() }.isEmpty())) downloadAllTags().also {
_allTags = it
} else _allTags!!
fun transform(galleryBlock: GalleryBlock): SearchResult = fun transform(galleryBlock: GalleryBlock): SearchResult =
SearchResult( SearchResult(
galleryBlock.id, galleryBlock.id,

View File

@@ -18,26 +18,11 @@
package xyz.quaver.pupil.types package xyz.quaver.pupil.types
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.hitomi.Suggestion
import xyz.quaver.pupil.util.translations
@Parcelize @Parcelize
data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion { class HistorySuggestion(override val body: String) : SearchSuggestion
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
@IgnoredOnParcel
override val body =
if (translations[s] != null)
"${translations[s]} ($s)"
else
s
}
@Parcelize
class Suggestion(override val body: String) : SearchSuggestion
@Parcelize @Parcelize
class NoResultSuggestion(override val body: String) : SearchSuggestion class NoResultSuggestion(override val body: String) : SearchSuggestion

View File

@@ -47,7 +47,6 @@ import kotlinx.coroutines.channels.Channel
import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.FloatingSearchView
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.floatingsearchview.util.view.SearchInputView
import xyz.quaver.hitomi.getSuggestionsForQuery
import xyz.quaver.pupil.* import xyz.quaver.pupil.*
import xyz.quaver.pupil.adapters.SearchResultsAdapter import xyz.quaver.pupil.adapters.SearchResultsAdapter
import xyz.quaver.pupil.databinding.MainActivityBinding import xyz.quaver.pupil.databinding.MainActivityBinding
@@ -77,14 +76,14 @@ class MainActivity :
private var query = "" private var query = ""
set(value) { set(value) {
field = value field = value
with(findViewById<SearchInputView>(R.id.search_bar_text)) { with (findViewById<SearchInputView>(R.id.search_bar_text)) {
if (text.toString() != value) if (text.toString() != value)
setText(query, TextView.BufferType.EDITABLE) setText(query, TextView.BufferType.EDITABLE)
} }
} }
private var queryStack = mutableListOf<String>() private var queryStack = mutableListOf<String>()
private lateinit var source: Source<*> private lateinit var source: Source<*, SearchSuggestion>
private lateinit var sortMode: Enum<*> private lateinit var sortMode: Enum<*>
private var searchJob: Deferred<Pair<Channel<SearchResult>, Int>>? = null private var searchJob: Deferred<Pair<Channel<SearchResult>, Int>>? = null
@@ -168,7 +167,7 @@ class MainActivity :
} }
} }
private fun setSource(source: Source<*>) { private fun setSource(source: Source<*, SearchSuggestion>) {
this.source = source this.source = source
sortMode = source.availableSortMode.first() sortMode = source.availableSortMode.first()
@@ -219,14 +218,14 @@ class MainActivity :
//NavigationView //NavigationView
binding.navView.setNavigationItemSelectedListener(this) binding.navView.setNavigationItemSelectedListener(this)
with(binding.contents.cancelFab) { with (binding.contents.cancelFab) {
setImageResource(R.drawable.cancel) setImageResource(R.drawable.cancel)
setOnClickListener { setOnClickListener {
DownloadService.cancel(this@MainActivity) DownloadService.cancel(this@MainActivity)
} }
} }
with(binding.contents.jumpFab) { with (binding.contents.jumpFab) {
setImageResource(R.drawable.ic_jump) setImageResource(R.drawable.ic_jump)
setOnClickListener { setOnClickListener {
val perPage = Preferences["per_page", "25"].toInt() val perPage = Preferences["per_page", "25"].toInt()
@@ -250,7 +249,7 @@ class MainActivity :
} }
} }
with(binding.contents.randomFab) { with (binding.contents.randomFab) {
setImageResource(R.drawable.shuffle_variant) setImageResource(R.drawable.shuffle_variant)
setOnClickListener { setOnClickListener {
setImageDrawable(CircularProgressDrawable(context)) setImageDrawable(CircularProgressDrawable(context))
@@ -259,7 +258,7 @@ class MainActivity :
val random = Random.Default.nextInt(totalItems) val random = Random.Default.nextInt(totalItems)
val randomResult = val randomResult =
source.query( source.search(
query + Preferences["default_query", ""], query + Preferences["default_query", ""],
random .. random, random .. random,
sortMode sortMode
@@ -281,7 +280,7 @@ class MainActivity :
} }
} }
with(binding.contents.idFab) { with (binding.contents.idFab) {
setImageResource(R.drawable.numeric) setImageResource(R.drawable.numeric)
setOnClickListener { setOnClickListener {
val editText = EditText(context).apply { val editText = EditText(context).apply {
@@ -309,7 +308,7 @@ class MainActivity :
} }
} }
with(binding.contents.swipePageTurnView) { with (binding.contents.swipePageTurnView) {
setOnPageTurnListener(object: SwipePageTurnView.OnPageTurnListener { setOnPageTurnListener(object: SwipePageTurnView.OnPageTurnListener {
override fun onPrev(page: Int) { override fun onPrev(page: Int) {
currentPage-- currentPage--
@@ -344,7 +343,7 @@ class MainActivity :
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(binding.contents.recyclerview) { with (binding.contents.recyclerview) {
adapter = SearchResultsAdapter(searchResults).apply { adapter = SearchResultsAdapter(searchResults).apply {
onChipClickedHandler = { onChipClickedHandler = {
query = it.toQuery() query = it.toQuery()
@@ -406,24 +405,9 @@ class MainActivity :
} }
} }
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(10) + FavoriteHistorySwitch(getString(R.string.search_show_tags))
}
}.reversed()
private var suggestionJob : Job? = null private var suggestionJob : Job? = null
private fun setupSearchBar() { private fun setupSearchBar() {
with(binding.contents.searchview) { with (binding.contents.searchview) {
onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener { onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener {
override fun onMenuOpened() { override fun onMenuOpened() {
(this@MainActivity.binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems() (this@MainActivity.binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems()
@@ -434,15 +418,6 @@ class MainActivity :
} }
} }
onHistoryDeleteClickedListener = {
searchHistory.remove(it)
swapSuggestions(defaultSuggestions)
}
onFavoriteHistorySwitchClickListener = {
isFavorite = !isFavorite
swapSuggestions(defaultSuggestions)
}
onMenuItemClickListener = { onMenuItemClickListener = {
onActionMenuItemSelected(it) onActionMenuItemSelected(it)
} }
@@ -452,12 +427,6 @@ class MainActivity :
suggestionJob?.cancel() suggestionJob?.cancel()
if (query.isEmpty() or query.endsWith(' ')) {
swapSuggestions(defaultSuggestions)
return@lambda
}
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString()))) swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
val currentQuery = query.split(" ").last() val currentQuery = query.split(" ").last()
@@ -466,16 +435,8 @@ class MainActivity :
suggestionJob = CoroutineScope(Dispatchers.IO).launch { suggestionJob = CoroutineScope(Dispatchers.IO).launch {
val suggestions = kotlin.runCatching { val suggestions = kotlin.runCatching {
getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }.toMutableList() source.suggestion(currentQuery)
}.getOrElse { mutableListOf() } }.getOrElse { emptyList() }
suggestions.filter {
val tag = "${it.n}:${it.s.replace(Regex("\\s"), "_")}"
favoriteTags.contains(Tag.parse(tag))
}.reversed().forEach {
suggestions.remove(it)
suggestions.add(0, it)
}
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString()))) swapSuggestions(if (suggestions.isNotEmpty()) suggestions else listOf(NoResultSuggestion(getText(R.string.main_no_result).toString())))
@@ -483,10 +444,13 @@ class MainActivity :
} }
} }
onSuggestionBinding = { binding, item ->
source.onSuggestionBind(binding, item)
}
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener { onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
override fun onFocus() { override fun onFocus() {
if (query.isEmpty() or query.endsWith(' '))
swapSuggestions(defaultSuggestions)
} }
override fun onFocusCleared() { override fun onFocusCleared() {
@@ -577,7 +541,7 @@ class MainActivity :
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
searchJob = async(Dispatchers.IO) { searchJob = async(Dispatchers.IO) {
source.query( source.search(
query + Preferences["default_query", ""], query + Preferences["default_query", ""],
(currentPage - 1) * perPage until currentPage * perPage, (currentPage - 1) * perPage until currentPage * perPage,
sortMode sortMode

View File

@@ -132,7 +132,7 @@ class ReaderActivity : BaseActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean { override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.reader, menu) menuInflater.inflate(R.menu.reader, menu)
with(menu?.findItem(R.id.reader_menu_favorite)) { with (menu?.findItem(R.id.reader_menu_favorite)) {
this ?: return@with this ?: return@with
if (favorites.contains(galleryID)) if (favorites.contains(galleryID))
@@ -149,7 +149,7 @@ class ReaderActivity : BaseActivity() {
// TODO: Switch to DialogFragment // TODO: Switch to DialogFragment
val binding = NumberpickerDialogBinding.inflate(layoutInflater, binding.root, false) val binding = NumberpickerDialogBinding.inflate(layoutInflater, binding.root, false)
with(binding.numberPicker) { with (binding.numberPicker) {
minValue = 1 minValue = 1
maxValue = cache.metadata.reader?.files?.size ?: 0 maxValue = cache.metadata.reader?.files?.size ?: 0
value = currentPage value = currentPage
@@ -265,7 +265,7 @@ class ReaderActivity : BaseActivity() {
val reader = cache.metadata.reader val reader = cache.metadata.reader
if (reader != null) { if (reader != null) {
with(binding.recyclerview.adapter as ReaderAdapter) { with (binding.recyclerview.adapter as ReaderAdapter) {
this.reader = reader this.reader = reader
notifyDataSetChanged() notifyDataSetChanged()
} }
@@ -297,7 +297,7 @@ class ReaderActivity : BaseActivity() {
} }
private fun initView() { private fun initView() {
with(binding.recyclerview) { with (binding.recyclerview) {
adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply { adapter = ReaderAdapter(this@ReaderActivity, galleryID).apply {
onItemClickListener = { onItemClickListener = {
if (isScroll) { if (isScroll) {
@@ -331,7 +331,7 @@ class ReaderActivity : BaseActivity() {
}) })
} }
with(binding.downloadFab) { with (binding.downloadFab) {
animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button animateDownloadFAB(DownloadManager.getInstance(this@ReaderActivity).getDownloadFolder(galleryID) != null) //If download in progress, animate button
setOnClickListener { setOnClickListener {
@@ -348,14 +348,14 @@ class ReaderActivity : BaseActivity() {
} }
} }
with(binding.retryFab) { with (binding.retryFab) {
setImageResource(R.drawable.refresh) setImageResource(R.drawable.refresh)
setOnClickListener { setOnClickListener {
DownloadService.download(context, galleryID) DownloadService.download(context, galleryID)
} }
} }
with(binding.fullscreenFab) { with (binding.fullscreenFab) {
setImageResource(R.drawable.ic_fullscreen) setImageResource(R.drawable.ic_fullscreen)
setOnClickListener { setOnClickListener {
isFullscreen = true isFullscreen = true
@@ -367,7 +367,7 @@ class ReaderActivity : BaseActivity() {
} }
private fun fullscreen(isFullscreen: Boolean) { private fun fullscreen(isFullscreen: Boolean) {
with(window.attributes) { with (window.attributes) {
if (isFullscreen) { if (isFullscreen) {
flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN flags = flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
supportActionBar?.hide() supportActionBar?.hide()
@@ -413,7 +413,7 @@ class ReaderActivity : BaseActivity() {
} }
private fun animateDownloadFAB(animate: Boolean) { private fun animateDownloadFAB(animate: Boolean) {
with(binding.downloadFab) { with (binding.downloadFab) {
if (animate) { if (animate) {
val icon = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_downloading) val icon = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_downloading)

View File

@@ -27,7 +27,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.DefaultQueryDialogBinding import xyz.quaver.pupil.databinding.DefaultQueryDialogBinding
import xyz.quaver.pupil.sources.hitomi.Hitomi import xyz.quaver.pupil.sources.Hitomi
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
@@ -56,7 +56,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
setPositiveButton(android.R.string.ok) { _, _ -> setPositiveButton(android.R.string.ok) { _, _ ->
val newTags = Tags.parse(binding.edittext.text.toString()) val newTags = Tags.parse(binding.edittext.text.toString())
with(binding.languageSelector) { with (binding.languageSelector) {
if (selectedItemPosition != 0) if (selectedItemPosition != 0)
newTags.add("language:${reverseLanguages[selectedItem]}") newTags.add("language:${reverseLanguages[selectedItem]}")
} }
@@ -89,7 +89,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
Preferences["default_query"] Preferences["default_query"]
) )
with(binding.languageSelector) { with (binding.languageSelector) {
adapter = adapter =
ArrayAdapter( ArrayAdapter(
context, context,
@@ -112,13 +112,13 @@ class DefaultQueryDialogFragment() : DialogFragment() {
} }
} }
with(binding.BLCheckbox) { with (binding.BLCheckbox) {
isChecked = tags.contains(excludeBL) isChecked = tags.contains(excludeBL)
if (tags.contains(excludeBL)) if (tags.contains(excludeBL))
tags.remove(excludeBL) tags.remove(excludeBL)
} }
with(binding.guroCheckbox) { with (binding.guroCheckbox) {
isChecked = excludeGuro.all { tags.contains(it) } isChecked = excludeGuro.all { tags.contains(it) }
if (excludeGuro.all { tags.contains(it) }) if (excludeGuro.all { tags.contains(it) })
excludeGuro.forEach { excludeGuro.forEach {
@@ -126,7 +126,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
} }
} }
with(binding.loliCheckbox) { with (binding.loliCheckbox) {
isChecked = excludeLoli.all { tags.contains(it) } isChecked = excludeLoli.all { tags.contains(it) }
if (excludeLoli.all { tags.contains(it) }) if (excludeLoli.all { tags.contains(it) })
excludeLoli.forEach { excludeLoli.forEach {
@@ -134,7 +134,7 @@ class DefaultQueryDialogFragment() : DialogFragment() {
} }
} }
with(binding.edittext) { with (binding.edittext) {
setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE) setText(tags.toString(), android.widget.TextView.BufferType.EDITABLE)
addTextChangedListener(object : TextWatcher { addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged( override fun beforeTextChanged(

View File

@@ -42,7 +42,7 @@ import xyz.quaver.pupil.adapters.SearchResultsAdapter
import xyz.quaver.pupil.adapters.ThumbnailPageAdapter import xyz.quaver.pupil.adapters.ThumbnailPageAdapter
import xyz.quaver.pupil.databinding.* import xyz.quaver.pupil.databinding.*
import xyz.quaver.pupil.favoriteTags import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.sources.hitomi.Hitomi import xyz.quaver.pupil.sources.Hitomi
import xyz.quaver.pupil.sources.SearchResult import xyz.quaver.pupil.sources.SearchResult
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.ui.ReaderActivity import xyz.quaver.pupil.ui.ReaderActivity
@@ -66,7 +66,7 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial
window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
with(binding.fab) { with (binding.fab) {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right)) setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
setOnClickListener { setOnClickListener {
context.startActivity(Intent(context, ReaderActivity::class.java).apply { context.startActivity(Intent(context, ReaderActivity::class.java).apply {
@@ -84,7 +84,7 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial
binding.title.text = gallery.title binding.title.text = gallery.title
binding.artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() } binding.artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
with(binding.type) { with (binding.type) {
text = gallery.type.wordCapitalize() text = gallery.type.wordCapitalize()
setOnClickListener { setOnClickListener {
gallery.type.let { gallery.type.let {

View File

@@ -67,7 +67,7 @@ class ProxyDialog(context: Context) : AlertDialog(context) {
} }
} }
with(binding.typeSelector) { with (binding.typeSelector) {
adapter = ArrayAdapter( adapter = ArrayAdapter(
context, context,
android.R.layout.simple_spinner_dropdown_item, android.R.layout.simple_spinner_dropdown_item,

View File

@@ -25,13 +25,14 @@ import android.view.Window
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.adapters.SourceAdapter import xyz.quaver.pupil.adapters.SourceAdapter
import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.sources import xyz.quaver.pupil.sources.sources
class SourceSelectDialog : DialogFragment() { class SourceSelectDialog : DialogFragment() {
var onSourceSelectedListener: ((Source<*>) -> Unit)? = null var onSourceSelectedListener: ((Source<*, SearchSuggestion>) -> Unit)? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return Dialog(requireContext()).apply { return Dialog(requireContext()).apply {

View File

@@ -60,7 +60,7 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.lock_preferences, rootKey) setPreferencesFromResource(R.xml.lock_preferences, rootKey)
with(findPreference<Preference>("lock_pattern")) { with (findPreference<Preference>("lock_pattern")) {
this!! this!!
if (LockManager(requireContext()).contains(Lock.Type.PATTERN)) if (LockManager(requireContext()).contains(Lock.Type.PATTERN))
@@ -93,7 +93,7 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
} }
} }
with(findPreference<Preference>("lock_pin")) { with (findPreference<Preference>("lock_pin")) {
this!! this!!
if (LockManager(requireContext()).contains(Lock.Type.PIN)) if (LockManager(requireContext()).contains(Lock.Type.PIN))
@@ -126,7 +126,7 @@ class LockSettingsFragment : PreferenceFragmentCompat() {
} }
} }
with(findPreference<Preference>("lock_fingerprint")) { with (findPreference<Preference>("lock_fingerprint")) {
this!! this!!
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->

View File

@@ -48,7 +48,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
override fun onPreferenceClick(preference: Preference?): Boolean { override fun onPreferenceClick(preference: Preference?): Boolean {
val context = context ?: return false val context = context ?: return false
with(preference) { with (preference) {
this ?: return false this ?: return false
when (key) { when (key) {
@@ -141,7 +141,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
private fun initPreferences() { private fun initPreferences() {
val context = context ?: return val context = context ?: return
with(findPreference<Preference>("delete_cache")) { with (findPreference<Preference>("delete_cache")) {
this ?: return@with this ?: return@with
val dir = File(context.cacheDir, "imageCache") val dir = File(context.cacheDir, "imageCache")
@@ -162,7 +162,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
onPreferenceClickListener = this@ManageStorageFragment onPreferenceClickListener = this@ManageStorageFragment
} }
with(findPreference<Preference>("delete_downloads")) { with (findPreference<Preference>("delete_downloads")) {
this ?: return@with this ?: return@with
val dir = DownloadManager.getInstance(context).downloadFolder val dir = DownloadManager.getInstance(context).downloadFolder
@@ -184,7 +184,7 @@ class ManageStorageFragment : PreferenceFragmentCompat(), Preference.OnPreferenc
onPreferenceClickListener = this@ManageStorageFragment onPreferenceClickListener = this@ManageStorageFragment
} }
with(findPreference<Preference>("clear_history")) { with (findPreference<Preference>("clear_history")) {
this ?: return@with this ?: return@with
summary = context.getString(R.string.settings_clear_history_summary, histories.size) summary = context.getString(R.string.settings_clear_history_summary, histories.size)

View File

@@ -155,7 +155,7 @@ class SettingsFragment :
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
key ?: return key ?: return
with(findPreference<Preference>(key)) { with (findPreference<Preference>(key)) {
this ?: return this ?: return
when (key) { when (key) {

View File

@@ -28,18 +28,20 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo import android.view.inputmethod.EditorInfo
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.swiperefreshlayout.widget.CircularProgressDrawable import androidx.swiperefreshlayout.widget.CircularProgressDrawable
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import xyz.quaver.floatingsearchview.FloatingSearchView import xyz.quaver.floatingsearchview.FloatingSearchView
import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.floatingsearchview.util.view.SearchInputView
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.SuggestionCountBinding
import xyz.quaver.pupil.favoriteTags import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.sources.DefaultSearchSuggestion
import xyz.quaver.pupil.sources.Hitomi
import xyz.quaver.pupil.types.* import xyz.quaver.pupil.types.*
import java.util.* import java.util.*
@@ -53,13 +55,15 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
var onHistoryDeleteClickedListener: ((String) -> Unit)? = null var onHistoryDeleteClickedListener: ((String) -> Unit)? = null
var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null var onFavoriteHistorySwitchClickListener: (() -> Unit)? = null
var onSuggestionBinding: ((SearchSuggestionItemBinding, SearchSuggestion) -> Unit)? = null
init { init {
searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or searchInputView.imeOptions searchInputView.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI or searchInputView.imeOptions
searchInputView.addTextChangedListener(this) searchInputView.addTextChangedListener(this)
onSearchListener = this onSearchListener = this
onBindSuggestionCallback = { a, b, c, d, e -> onBindSuggestionCallback = { binding, item, _ ->
onBindSuggestion(a, b, c, d, e) onBindSuggestion(binding, item)
} }
} }
@@ -80,17 +84,17 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) { override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
when (searchSuggestion) { when (searchSuggestion) {
is TagSuggestion -> { is Hitomi.TagSuggestion -> {
val tag = "${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")}" val tag = "${searchSuggestion.n}:${searchSuggestion.s.replace(Regex("\\s"), "_")}"
with(searchInputView.text!!) { with (searchInputView.text!!) {
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ') + 1, length) delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ') + 1, length)
if (!this.contains(tag)) if (!this.contains(tag))
append("$tag ") append("$tag ")
} }
} }
is Suggestion -> { is HistorySuggestion -> {
with(searchInputView.text!!) { with (searchInputView.text!!) {
clear() clear()
append(searchSuggestion.body) append(searchSuggestion.body)
} }
@@ -101,114 +105,23 @@ class FloatingSearchView @JvmOverloads constructor(context: Context, attrs: Attr
override fun onSearchAction(currentQuery: String?) {} override fun onSearchAction(currentQuery: String?) {}
fun onBindSuggestion( private fun onBindSuggestion(binding: SearchSuggestionItemBinding, item: SearchSuggestion) {
suggestionView: View?,
leftIcon: ImageView?,
textView: TextView?,
item: SearchSuggestion?,
itemPosition: Int
) {
when(item) { 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 > 0) {
(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 -> { is FavoriteHistorySwitch -> {
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.swap_horizontal, context.theme)) binding.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.body)
}
}
} }
is LoadingSuggestion -> { is LoadingSuggestion -> {
leftIcon?.setImageDrawable(CircularProgressDrawable(context).also { binding.leftIcon.setImageDrawable(CircularProgressDrawable(context).also {
it.setStyle(CircularProgressDrawable.DEFAULT) it.setStyle(CircularProgressDrawable.DEFAULT)
it.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN) it.colorFilter = PorterDuffColorFilter(ContextCompat.getColor(context, R.color.colorAccent), PorterDuff.Mode.SRC_IN)
it.start() it.start()
}) })
} }
is NoResultSuggestion -> { is NoResultSuggestion -> {
leftIcon?.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.close, context.theme)) binding.leftIcon.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.close, context.theme))
}
else -> {
onSuggestionBinding?.invoke(binding, item)
} }
} }
} }

View File

@@ -24,7 +24,7 @@ import androidx.core.content.ContextCompat
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.favoriteTags import xyz.quaver.pupil.favoriteTags
import xyz.quaver.pupil.sources.hitomi.Hitomi import xyz.quaver.pupil.sources.Hitomi
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.translations import xyz.quaver.pupil.util.translations
import xyz.quaver.pupil.util.wordCapitalize import xyz.quaver.pupil.util.wordCapitalize