Added History

This commit is contained in:
tom5079
2021-02-21 10:34:26 +09:00
parent 80b7293879
commit a1c6d87c54
14 changed files with 95 additions and 130 deletions

View File

@@ -18,6 +18,7 @@
<package name="" alias="true" withSubpackages="true" /> <package name="" alias="true" withSubpackages="true" />
</value> </value>
</option> </option>
<option name="ALLOW_TRAILING_COMMA" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<codeStyleSettings language="XML"> <codeStyleSettings language="XML">

View File

@@ -81,5 +81,10 @@
<option name="name" value="maven2" /> <option name="name" value="maven2" />
<option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" /> <option name="url" value="https://oss.sonatype.org/content/repositories/snapshots" />
</remote-repository> </remote-repository>
<remote-repository>
<option name="id" value="MavenLocal" />
<option name="name" value="MavenLocal" />
<option name="url" value="file:/$USER_HOME$/.m2/repository/" />
</remote-repository>
</component> </component>
</project> </project>

View File

@@ -134,9 +134,9 @@ dependencies {
implementation "ru.noties.markwon:core:3.1.0" implementation "ru.noties.markwon:core:3.1.0"
implementation "xyz.quaver:libpupil:1.9.7-SNAPSHOT" 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.1.1" implementation "xyz.quaver:floatingsearchview:1.1.3-SNAPSHOT"
implementation "com.orhanobut:logger:2.2.0" implementation "com.orhanobut:logger:2.2.0"

View File

@@ -55,7 +55,7 @@ class MirrorAdapter(context: Context) : RecyclerView.Adapter<MirrorAdapter.ViewH
val list = mirrors.keys.toMutableList().apply { val list = mirrors.keys.toMutableList().apply {
Preferences.get<String>("mirrors") Preferences.get<String>("mirrors")
.split(">") .split(">")
.reversed() .asReversed()
.forEach { .forEach {
if (this.contains(it)) { if (this.contains(it)) {
this.remove(it) this.remove(it)

View File

@@ -18,15 +18,12 @@
package xyz.quaver.pupil.sources package xyz.quaver.pupil.sources
import android.util.Log
import com.orhanobut.logger.Logger
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.kodein.di.DI import org.kodein.di.DI
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.di
import org.kodein.di.instance import org.kodein.di.instance
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.util.SavedSourceSet import xyz.quaver.pupil.util.SavedSourceSet
@@ -47,9 +44,11 @@ class History(override val di: DI, source: String) : Source<DefaultSortMode, Sea
val channel = Channel<ItemInfo>() val channel = Channel<ItemInfo>()
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
histories.map[source.name]?.forEach { histories[source.name]?.asReversed()?.forEach {
channel.send(source.info(it)) channel.send(source.info(it))
} }
channel.close()
} }
return Pair(channel, histories.map.size) return Pair(channel, histories.map.size)

View File

@@ -148,6 +148,7 @@ class MainActivity :
} else { } else {
binding.contents.progressbar.hide() binding.contents.progressbar.hide()
if (it.isEmpty()) { if (it.isEmpty()) {
binding.contents.recyclerview.adapter?.notifyDataSetChanged()
binding.contents.noresult.show() binding.contents.noresult.show()
} else { } else {
binding.contents.recyclerview.adapter?.notifyItemInserted(it.size-1) binding.contents.recyclerview.adapter?.notifyItemInserted(it.size-1)
@@ -396,7 +397,7 @@ class MainActivity :
onActionMenuItemSelected(it) onActionMenuItemSelected(it)
} }
onQueryChangeListener = lambda@{ _, query -> onQueryChangeListener = { _, query ->
model.query.value = query model.query.value = query
model.suggestion() model.suggestion()
@@ -404,9 +405,7 @@ class MainActivity :
swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString()))) swapSuggestions(listOf(LoadingSuggestion(getText(R.string.reader_loading).toString())))
} }
onSuggestionBinding = { binding, item -> onSuggestionBinding = model.source.value!!::onSuggestionBind
model.source.value!!.onSuggestionBind(binding, item)
}
onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener { onFocusChangeListener = object: FloatingSearchView.OnFocusChangeListener {
override fun onFocus() { override fun onFocus() {
@@ -450,24 +449,13 @@ class MainActivity :
binding.drawer.closeDrawers() binding.drawer.closeDrawers()
when(item.itemId) { when(item.itemId) {
R.id.main_drawer_history -> { R.id.main_drawer_home -> model.setModeAndReset(MainViewModel.MainMode.SEARCH)
//model.setSourceAndReset(direct.instance<String, History>(arg = source.name)) R.id.main_drawer_history -> model.setModeAndReset(MainViewModel.MainMode.HISTORY)
} R.id.main_drawer_help -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help))))
R.id.main_drawer_help -> { R.id.main_drawer_github -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.github))))
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.help)))) 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_github -> { R.id.main_drawer_kakaotalk -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.discord))))
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.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))))
}
} }
} }

View File

@@ -141,7 +141,7 @@ class ReaderActivity : BaseActivity(), DIAware {
menu?.forEach { menu?.forEach {
when (it.itemId) { when (it.itemId) {
R.id.reader_menu_favorite -> { R.id.reader_menu_favorite -> {
if (favorites.map[source]?.contains(itemID) == true) if (favorites[source]?.contains(itemID) == true)
(it.icon as Animatable).start() (it.icon as Animatable).start()
} }
R.id.source -> { R.id.source -> {
@@ -160,7 +160,7 @@ class ReaderActivity : BaseActivity(), DIAware {
val id = itemID val id = itemID
val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true val favorite = menu?.findItem(R.id.reader_menu_favorite) ?: return true
if (favorites.map[source]?.contains(id) == true) { if (favorites[source]?.contains(id) == true) {
favorites.remove(source, id) favorites.remove(source, id)
favorite.icon = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_star) favorite.icon = AnimatedVectorDrawableCompat.create(this, R.drawable.avd_star)
} else { } else {

View File

@@ -75,7 +75,7 @@ class TagChip(context: Context, private val source: String, _tag: Tag) : Chip(co
) )
setOnCloseIconClickListener { setOnCloseIconClickListener {
if (favoriteTags.map[source]?.contains(tag.toString()) == true) { if (favoriteTags[source]?.contains(tag.toString()) == true) {
favoriteTags.remove(source, tag.toString()) favoriteTags.remove(source, tag.toString())
closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_empty) closeIcon = ContextCompat.getDrawable(context, R.drawable.ic_star_empty)

View File

@@ -25,8 +25,10 @@ import kotlinx.coroutines.*
import org.kodein.di.DIAware import org.kodein.di.DIAware
import org.kodein.di.android.x.di import org.kodein.di.android.x.di
import org.kodein.di.direct import org.kodein.di.direct
import org.kodein.di.instance
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.pupil.sources.AnySource import xyz.quaver.pupil.sources.AnySource
import xyz.quaver.pupil.sources.History
import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.ItemInfo
import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.Preferences
import xyz.quaver.pupil.util.notify import xyz.quaver.pupil.util.notify
@@ -37,7 +39,6 @@ import kotlin.random.Random
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
override val di by di() override val di by di()
private val _searchResults = MutableLiveData<MutableList<ItemInfo>>() private val _searchResults = MutableLiveData<MutableList<ItemInfo>>()
@@ -51,6 +52,10 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
val query = MutableLiveData<String>() val query = MutableLiveData<String>()
private val queryStack = mutableListOf<String>() private val queryStack = mutableListOf<String>()
private val defaultSourceFactory: (String) -> AnySource = {
direct.source(it)
}
private var sourceFactory: (String) -> AnySource = defaultSourceFactory
private val _source = MutableLiveData<AnySource>() private val _source = MutableLiveData<AnySource>()
val source: LiveData<AnySource> = _source val source: LiveData<AnySource> = _source
@@ -82,7 +87,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
} }
fun setSourceAndReset(sourceName: String) { fun setSourceAndReset(sourceName: String) {
_source.value = direct.source(sourceName).also { _source.value = sourceFactory(sourceName).also {
sortMode.value = it.availableSortMode.first() sortMode.value = it.availableSortMode.first()
} }
@@ -97,6 +102,16 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
query() query()
} }
fun setModeAndReset(mode: MainMode) {
sourceFactory = when (mode) {
MainMode.SEARCH -> defaultSourceFactory
MainMode.HISTORY -> { { direct.instance<String, History>(arg = it) } }
else -> return
}
setSourceAndReset(source.value!!.name)
}
fun query() { fun query() {
val perPage = Preferences["per_page", "25"].toInt() val perPage = Preferences["per_page", "25"].toInt()
val source = _source.value ?: error("Source is null") val source = _source.value ?: error("Source is null")
@@ -181,4 +196,11 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware {
return true return true
} }
enum class MainMode {
SEARCH,
HISTORY,
DOWNLOADS,
FAVORITES
}
} }

View File

@@ -21,81 +21,14 @@ package xyz.quaver.pupil.util
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.ListSerializer
import kotlinx.serialization.builtins.MapSerializer import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.SetSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.Json.Default.decodeFromString import kotlinx.serialization.json.Json.Default.decodeFromString
import kotlinx.serialization.serializer import kotlinx.serialization.serializer
import java.io.File import java.io.File
class SavedSet <T: Any> (private val file: File, any: T, private val set: MutableSet<T> = mutableSetOf()) : MutableSet<T> by set {
@Suppress("UNCHECKED_CAST")
@OptIn(ExperimentalSerializationApi::class)
val serializer: KSerializer<Set<T>> = SetSerializer(serializer(any::class.java) as KSerializer<T>)
init {
if (!file.exists()) {
save()
}
load()
}
@Synchronized
fun load() {
set.clear()
kotlin.runCatching {
decodeFromString(serializer, file.readText())
}.onSuccess {
set.addAll(it)
}
}
@Synchronized
fun save() {
file.parentFile?.mkdirs()
if (!file.exists())
file.createNewFile()
file.writeText(Json.encodeToString(serializer, set))
}
@Synchronized
override fun add(element: T): Boolean {
set.remove(element)
return set.add(element).also {
save()
}
}
@Synchronized
override fun addAll(elements: Collection<T>): Boolean {
set.removeAll(elements)
return set.addAll(elements).also {
save()
}
}
@Synchronized
override fun remove(element: T): Boolean {
load()
return set.remove(element).also {
save()
}
}
@Synchronized
override fun clear() {
set.clear()
save()
}
}
class SavedMap <K: Any, V: Any> (private val file: File, anyKey: K, anyValue: V, private val map: MutableMap<K, V> = mutableMapOf()) : MutableMap<K, V> by map { class SavedMap <K: Any, V: Any> (private val file: File, anyKey: K, anyValue: V, private val map: MutableMap<K, V> = mutableMapOf()) : MutableMap<K, V> by map {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -172,11 +105,10 @@ class SavedMap <K: Any, V: Any> (private val file: File, anyKey: K, anyValue: V,
} }
class SavedSourceSet(private val file: File) { class SavedSourceSet(private val file: File) {
private val _map = mutableMapOf<String, MutableList<String>>()
val map: Map<String, List<String>> = _map
private val _map = mutableMapOf<String, MutableSet<String>>() private val serializer = MapSerializer(String.serializer(), ListSerializer(String.serializer()))
val map: Map<String, Set<String>> = _map
private val serializer = MapSerializer(String.serializer(), SetSerializer(String.serializer()))
@Synchronized @Synchronized
fun load() { fun load() {
@@ -185,7 +117,7 @@ class SavedSourceSet(private val file: File) {
decodeFromString(serializer, file.readText()) decodeFromString(serializer, file.readText())
}.onSuccess { }.onSuccess {
it.forEach { (k, v) -> it.forEach { (k, v) ->
_map[k] = v.toMutableSet() _map[k] = v.toMutableList()
} }
} }
} }
@@ -199,6 +131,8 @@ class SavedSourceSet(private val file: File) {
file.writeText(Json.encodeToString(serializer, _map)) file.writeText(Json.encodeToString(serializer, _map))
} }
operator fun get(key: String) = _map[key]
@Synchronized @Synchronized
fun add(source: String, value: String) { fun add(source: String, value: String) {
load() load()
@@ -206,7 +140,7 @@ class SavedSourceSet(private val file: File) {
_map[source]?.remove(value) _map[source]?.remove(value)
if (!_map.containsKey(source)) if (!_map.containsKey(source))
_map[source] = mutableSetOf() _map[source] = mutableListOf()
else else
_map[source]!!.add(value) _map[source]!!.add(value)
@@ -222,7 +156,7 @@ class SavedSourceSet(private val file: File) {
_map[source]!!.removeAll(from[source]!!) _map[source]!!.removeAll(from[source]!!)
_map[source]!!.addAll(from[source]!!) _map[source]!!.addAll(from[source]!!)
} else { } else {
_map[source] = from[source]!!.toMutableSet() _map[source] = from[source]!!.toMutableList()
} }
} }

View File

@@ -1,3 +1,21 @@
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2021 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/>.
-->
<!--drawable/clock_end.xml--> <!--drawable/clock_end.xml-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"> <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="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M15 16v3H3v2h12v3l4-4m0 0v4h2v-8h-2"/> <path android:fillColor="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M15 16v3H3v2h12v3l4-4m0 0v4h2v-8h-2"/>

View File

@@ -1,3 +1,21 @@
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2021 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/>.
-->
<!--drawable/clock_start.xml--> <!--drawable/clock_start.xml-->
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"> <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="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M4 16v8h2v-3h12v3l4-4-4-4v3H6v-3"/> <path android:fillColor="#fff" android:pathData="M12 1C8.14 1 5 4.14 5 8a7 7 0 0 0 7 7c3.86 0 7-3.13 7-7 0-3.86-3.14-7-7-7m0 2.15c2.67 0 4.85 2.17 4.85 4.85 0 2.68-2.18 4.85-4.85 4.85A4.85 4.85 0 0 1 7.15 8 4.85 4.85 0 0 1 12 3.15M11 5v3.69l3.19 1.84 0.75-1.3-2.44-1.41V5M4 16v8h2v-3h12v3l4-4-4-4v3H6v-3"/>

View File

@@ -19,24 +19,4 @@
<item>SOCKS</item> <item>SOCKS</item>
</string-array> </string-array>
<string-array name="cache_size">
<item>0</item>
<item>1</item>
<item>2</item>
<item>4</item>
<item>8</item>
<item>16</item>
<item>32</item>
</string-array>
<string-array name="cache_size_text">
<item>@string/settings_cache_unlimited</item>
<item>1G</item>
<item>2G</item>
<item>4G</item>
<item>8G</item>
<item>16G</item>
<item>32G</item>
</string-array>
</resources> </resources>

View File

@@ -13,7 +13,7 @@ buildscript {
classpath "com.google.gms:google-services:4.3.5" classpath "com.google.gms:google-services:4.3.5"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath "com.google.firebase:firebase-crashlytics-gradle:2.4.1" classpath "com.google.firebase:firebase-crashlytics-gradle:2.5.0"
classpath "com.google.firebase:perf-plugin:1.3.4" classpath "com.google.firebase:perf-plugin:1.3.4"
classpath "com.google.android.gms:oss-licenses-plugin:0.10.2" classpath "com.google.android.gms:oss-licenses-plugin:0.10.2"
} }