History functionality added

This commit is contained in:
tom5079
2019-05-13 20:07:42 +09:00
parent 720149c38a
commit 161d1bdcdd
22 changed files with 274 additions and 99 deletions

View File

@@ -19,6 +19,9 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
kotlinOptions {
freeCompilerArgs += '-Xuse-experimental=kotlin.Experimental'
}
}
dependencies {

View File

@@ -11,11 +11,9 @@ import kotlinx.coroutines.*
import xyz.quaver.hitomi.Reader
import xyz.quaver.hitomi.getReader
import xyz.quaver.hitomi.getReferer
import xyz.quaver.hiyobi.hiyobi
import xyz.quaver.pupil.adapters.GalleryAdapter
import java.io.File
import java.io.FileOutputStream
import java.lang.Exception
import java.net.URL
import javax.net.ssl.HttpsURLConnection

View File

@@ -15,7 +15,6 @@ import android.os.Environment
import android.preference.PreferenceManager
import android.text.*
import android.text.style.AlignmentSpan
import android.util.Log
import android.view.View
import android.view.WindowManager
import androidx.appcompat.app.AlertDialog
@@ -37,6 +36,7 @@ import kotlinx.coroutines.*
import xyz.quaver.hitomi.*
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.types.TagSuggestion
import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.SetLineOverlap
import xyz.quaver.pupil.util.checkUpdate
import xyz.quaver.pupil.util.getApkUrl
@@ -45,13 +45,16 @@ import javax.net.ssl.HttpsURLConnection
class MainActivity : AppCompatActivity() {
private val PERMISSION_REQUEST_CODE = 4585
private val permissionRequestCode = 4585
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
private var isLoading = false
private var query = ""
private var galleryIDs: Deferred<List<Int>>? = null
private var loadingJob: Job? = null
override fun onCreate(savedInstanceState: Bundle?) {
Histories.default = Histories(File(cacheDir, "histories.json"))
super.onCreate(savedInstanceState)
window.setFlags(
@@ -75,15 +78,33 @@ class MainActivity : AppCompatActivity() {
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
setOnRefreshListener {
runBlocking {
cleanJob?.join()
CoroutineScope(Dispatchers.Main).launch {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
fetchGalleries(query, true)
}
}
main_nav_view.setNavigationItemSelectedListener {
Log.d("Pupil", it.itemId.toString())
CoroutineScope(Dispatchers.Main).launch {
main_drawer_layout.closeDrawers()
cancelFetch()
clearGalleries()
when(it.itemId) {
R.id.main_drawer_home -> {
query = query.replace("HISTORY", "")
fetchGalleries(query)
}
R.id.main_drawer_history -> {
query += "HISTORY"
fetchGalleries(query)
}
}
loadBlocks()
}
true
}
@@ -91,6 +112,7 @@ class MainActivity : AppCompatActivity() {
setupRecyclerView()
setupSearchBar()
fetchGalleries(query)
loadBlocks()
}
override fun onBackPressed() {
@@ -113,7 +135,7 @@ class MainActivity : AppCompatActivity() {
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
else
ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE)
ActivityCompat.requestPermissions(this, permissions, permissionRequestCode)
}
}
@@ -183,6 +205,8 @@ class MainActivity : AppCompatActivity() {
//TODO: Maybe sprinke some transitions will be nice :D
startActivity(intent)
Histories.default.add(galleryID)
}
}
addOnScrollListener(
@@ -192,9 +216,9 @@ class MainActivity : AppCompatActivity() {
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (!isLoading)
if (loadingJob?.isActive != true)
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
fetchGalleries(query)
loadBlocks()
}
}
)
@@ -311,7 +335,9 @@ class MainActivity : AppCompatActivity() {
if (query != this@MainActivity.query) {
this@MainActivity.query = query
fetchGalleries(query, true)
cancelFetch()
clearGalleries()
fetchGalleries(query)
}
}
})
@@ -320,94 +346,96 @@ class MainActivity : AppCompatActivity() {
}
}
private val cache = ArrayList<Int>()
private var currentFetchingJob: Job? = null
private var cleanJob: Job? = null
private fun cancelFetch() {
isLoading = false
runBlocking {
cleanJob?.join()
currentFetchingJob?.cancelAndJoin()
galleryIDs?.cancelAndJoin()
loadingJob?.cancelAndJoin()
}
}
private fun fetchGalleries(query: String, clear: Boolean = false) {
private fun clearGalleries() {
galleries.clear()
main_recyclerview.adapter?.notifyDataSetChanged()
main_noresult.visibility = View.INVISIBLE
main_progressbar.show()
main_swipe_layout.isRefreshing = false
}
private fun fetchGalleries(query: String, from: Int = 0) {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
if (clear) {
cancelFetch()
cleanJob = CoroutineScope(Dispatchers.Main).launch {
cache.clear()
galleries.clear()
galleryIDs = null
main_recyclerview.adapter?.notifyDataSetChanged()
main_noresult.visibility = View.INVISIBLE
main_progressbar.show()
main_swipe_layout.isRefreshing = false
}
}
if (isLoading)
if (galleryIDs?.isActive == true)
return
isLoading = true
galleryIDs = CoroutineScope(Dispatchers.IO).async {
when {
query.contains("HISTORY") ->
Histories.default.toList()
query.isEmpty() and defaultQuery.isEmpty() ->
fetchNozomi(start = from, count = perPage)
else ->
doSearch("$defaultQuery $query")
}
}
}
currentFetchingJob = CoroutineScope(Dispatchers.IO).launch {
try {
val galleryIDs: List<Int>
private fun loadBlocks() {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
cleanJob?.join()
loadingJob = CoroutineScope(Dispatchers.IO).launch {
val galleryIDs = galleryIDs?.await()
if (query.isEmpty() && defaultQuery.isEmpty())
galleryIDs = fetchNozomi(start = galleries.size, count = perPage)
else {
if (cache.isEmpty())
cache.addAll(doSearch("$defaultQuery $query"))
galleryIDs = cache.slice(galleries.size until Math.min(galleries.size + perPage, cache.size))
with(main_recyclerview.adapter as GalleryBlockAdapter) {
noMore = galleries.size + perPage >= cache.size
}
if (galleryIDs.isNullOrEmpty()) { //No result
withContext(Dispatchers.Main) {
main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
}
if (query.isNotEmpty() and defaultQuery.isNotEmpty() and cache.isNullOrEmpty()) {
return@launch
}
if (query.isEmpty() and defaultQuery.isEmpty())
fetchGalleries("", galleries.size+perPage)
else
with(main_recyclerview.adapter as GalleryBlockAdapter) {
noMore = galleries.size + perPage >= galleryIDs.size
}
when {
query.isEmpty() and defaultQuery.isEmpty() ->
galleryIDs
else ->
galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size))
}.chunked(4).forEach { chunked ->
chunked.map {
async {
val galleryBlock = getGalleryBlock(it)
val thumbnail: Bitmap
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
thumbnail = BitmapFactory.decodeStream(inputStream)
}
Pair(galleryBlock, thumbnail)
}
}.forEach {
val galleryBlock = it.await()
withContext(Dispatchers.Main) {
main_noresult.visibility = View.VISIBLE
main_progressbar.hide()
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
}
galleryIDs.chunked(4).forEach { chunked ->
chunked.map {
async {
val galleryBlock = getGalleryBlock(it)
val thumbnail: Bitmap
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
thumbnail = BitmapFactory.decodeStream(inputStream)
}
Pair(galleryBlock, thumbnail)
}
}.forEach {
val galleryBlock = it.await()
withContext(Dispatchers.Main) {
main_progressbar.hide()
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
}
}
}
} finally {
isLoading = false
}
}
}

View File

@@ -7,6 +7,8 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import xyz.quaver.pupil.util.Histories
import java.io.File
class SettingsActivity : AppCompatActivity() {
@@ -35,8 +37,8 @@ class SettingsActivity : AppCompatActivity() {
"TB" //really?
)
private fun getCacheSize() : String {
var size = context!!.cacheDir.walk().map { it.length() }.sum()
private fun getCacheSize(dir: File) : String {
var size = dir.walk().map { it.length() }.sum()
var suffixIndex = 0
while (size >= 1024) {
@@ -50,22 +52,43 @@ class SettingsActivity : AppCompatActivity() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
with(findPreference<Preference>("delete_cache")) {
with(findPreference<Preference>("delete_image_cache")) {
this ?: return@with
summary = getCacheSize()
val dir = File(context.cacheDir, "imageCache")
summary = getCacheSize(dir)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_cache_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
with(context.cacheDir) {
if (exists())
deleteRecursively()
}
if (dir.exists())
dir.deleteRecursively()
summary = getCacheSize()
summary = getCacheSize(dir)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()
true
}
}
with(findPreference<Preference>("clear_history")) {
this ?: return@with
val histories = Histories.default
summary = getString(R.string.settings_clear_history_summary, histories.size)
setOnPreferenceClickListener {
AlertDialog.Builder(context).apply {
setTitle(R.string.warning)
setMessage(R.string.settings_clear_history_alert_message)
setPositiveButton(android.R.string.yes) { _, _ ->
histories.clear()
summary = getString(R.string.settings_clear_history_summary, histories.size)
}
setNegativeButton(android.R.string.no) { _, _ -> }
}.show()

View File

@@ -0,0 +1,74 @@
package xyz.quaver.pupil.util
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.parseList
import kotlinx.serialization.stringify
import java.io.File
class Histories(private val file: File) : ArrayList<Int>() {
init {
if (!file.exists())
file.parentFile.mkdirs()
try {
load()
} catch (e: Exception) {
save()
}
}
companion object {
lateinit var default: Histories
fun load(file: File) : Histories {
return Histories(file).load()
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun load() : Histories {
return apply {
super.clear()
addAll(
Json(JsonConfiguration.Stable).parseList(
file.bufferedReader().use { it.readText() }
)
)
}
}
@UseExperimental(ImplicitReflectionSerializer::class)
fun save() {
file.writeText(Json(JsonConfiguration.Stable).stringify(this))
}
override fun add(element: Int): Boolean {
load()
if (contains(element))
super.remove(element)
super.add(0, element)
save()
return true
}
override fun remove(element: Int): Boolean {
load()
val retval = super.remove(element)
save()
return retval
}
override fun clear() {
super.clear()
save()
}
}

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z"/>
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#333333">
<path
android:fillColor="#FF000000"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 585 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 314 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

View File

@@ -1,7 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="Test"
android:id="@+id/test"/>
<group android:checkableBehavior="single">
<item android:id="@+id/main_drawer_home"
android:title="@string/main_drawer_home"
android:checked="true"
android:icon="@drawable/ic_home"/>
<item android:id="@+id/main_drawer_history"
android:title="@string/main_drawer_history"
android:icon="@drawable/ic_history"/>
</group>
</menu>

View File

@@ -7,7 +7,7 @@
<string name="main_search">検索</string>
<string name="search_hint">ギャラリー検索</string>
<string name="settings_cache_title">キャッシュ</string>
<string name="settings_clear_cache">キャッシュクリア</string>
<string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string>
<string name="settings_default_query">デフォルト検索キーワード</string>
@@ -22,4 +22,9 @@
<string name="settings_image_loading_title">イメージロード</string>
<string name="settings_use_hiyobi_summary">ロード速度を向上させるためhiyobi.meからイメージロード</string>
<string name="settings_use_hiyobi_title">hiyobi.meからロード</string>
<string name="settings_clear_history">履歴の削除</string>
<string name="settings_clear_history_alert_message">履歴を削除しますか?</string>
<string name="settings_clear_history_summary">履歴数: %1$d</string>
<string name="main_drawer_history">履歴</string>
<string name="main_drawer_home">トップ</string>
</resources>

View File

@@ -6,7 +6,7 @@
<string name="permission_explain">권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다</string>
<string name="search_hint">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_cache">캐시 정리하기</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
<string name="settings_clear_cache_summary">현재 캐시 사용량: %1$d%2$s</string>
<string name="settings_galleries_per_page">한 번에 로드할 갤러리 수</string>
@@ -22,4 +22,9 @@
<string name="settings_image_loading_title">이미지 로딩</string>
<string name="settings_use_hiyobi_summary">속도 향상을 위해 가능하면 hiyobi.me에서 이미지 로드</string>
<string name="settings_use_hiyobi_title">hiyobi.me 사용</string>
<string name="settings_clear_history">히스토리 삭제</string>
<string name="settings_clear_history_alert_message">히스토리를 삭제하시겠습니까?</string>
<string name="settings_clear_history_summary">히스토리 %1$d개 저장됨</string>
<string name="main_drawer_history">히스토리</string>
<string name="main_drawer_home"></string>
</resources>

View File

@@ -18,6 +18,9 @@
<string name="main_search">Search</string>
<string name="main_no_result">No result</string>
<string name="main_drawer_home">Home</string>
<string name="main_drawer_history">History</string>
<string name="update_title">Update available</string>
<string name="update_message">Version %1$s is available!\n(current version is %2$s)\nDo you want to update?</string>
<string name="update_notification_description">Downloading apk&#8230;</string>
@@ -33,9 +36,12 @@
<string name="settings_galleries_per_page">Galleries per page</string>
<string name="settings_default_query">Default query</string>
<string name="settings_cache_title">Cache</string>
<string name="settings_clear_cache">Clear cache</string>
<string name="settings_clear_image_cache">Clear image cache</string>
<string name="settings_clear_cache_summary">Currently using %1$d%2$s of cache</string>
<string name="settings_clear_cache_alert_message">Deleting cache can affect image loading speed. Do you want to continue?</string>
<string name="settings_clear_history">Clear history</string>
<string name="settings_clear_history_alert_message">Do you want to clear histories?</string>
<string name="settings_clear_history_summary">%1$d histories saved</string>
<string name="settings_image_loading_title">Image Loading</string>
<string name="settings_use_hiyobi_title">Use hiyobi.me</string>
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>

View File

@@ -25,8 +25,12 @@
app:title="@string/settings_cache_title">
<Preference
app:title="@string/settings_clear_cache"
app:key="delete_cache"/>
app:title="@string/settings_clear_image_cache"
app:key="delete_image_cache"/>
<Preference
app:title="@string/settings_clear_history"
app:key="clear_history"/>
</PreferenceCategory>

View File

@@ -1,7 +1,7 @@
package xyz.quaver.pupil
import kotlinx.serialization.ImplicitReflectionSerializer
import org.junit.Test
import xyz.quaver.pupil.util.checkUpdate
/**
* Example local unit test, which will execute on the development machine (host).
@@ -12,8 +12,9 @@ import xyz.quaver.pupil.util.checkUpdate
class ExampleUnitTest {
@Test
@ImplicitReflectionSerializer
fun test() {
print(checkUpdate("https://api.github.com/repos/tom5079/Pupil-issue/releases", "0.0.1"))
}
}