Merge pull request #5 from tom5079/development

Pupil v2.2 Release
This commit is contained in:
tom5079
2019-06-02 01:04:50 +09:00
committed by GitHub
28 changed files with 767 additions and 309 deletions

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8 (2)" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" /> <output url="file://$PROJECT_DIR$/classes" />
</component> </component>
</project> </project>

View File

@@ -9,8 +9,8 @@ android {
applicationId "xyz.quaver.pupil" applicationId "xyz.quaver.pupil"
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 28 targetSdkVersion 28
versionCode 5 versionCode 8
versionName "1.4" versionName "2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
buildTypes { buildTypes {
@@ -38,6 +38,7 @@ dependencies {
implementation 'androidx.preference:preference:1.1.0-alpha05' implementation 'androidx.preference:preference:1.1.0-alpha05'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'com.github.arimorty:floatingsearchview:2.1.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1'
implementation 'com.github.deano2390:MaterialShowcaseView:1.3.4'
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation "ru.noties.markwon:core:${markwonVersion}" implementation "ru.noties.markwon:core:${markwonVersion}"

View File

@@ -6,16 +6,17 @@ import android.os.Bundle
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.text.* import android.text.*
import android.text.style.AlignmentSpan import android.text.style.AlignmentSpan
import android.view.LayoutInflater import android.util.Log
import android.view.View import android.view.*
import android.view.WindowManager import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arlib.floatingsearchview.FloatingSearchView import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView import com.arlib.floatingsearchview.util.view.SearchInputView
@@ -39,107 +40,64 @@ import java.net.URL
import java.util.* import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
enum class Mode {
SEARCH,
HISTORY,
DOWNLOAD
}
private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>() private val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
private var query = "" private var query = ""
private var mode = Mode.SEARCH
private val SETTINGS = 45162 private val SETTINGS = 45162
private var galleryIDs: Deferred<List<Int>>? = null private var galleryIDs: Deferred<List<Int>>? = null
private var totalItems = 0
private var loadingJob: Job? = null private var loadingJob: Job? = null
private var currentPage = 0
private lateinit var histories: Histories
private lateinit var downloads: Histories
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Histories.default = Histories(File(cacheDir, "histories.json"))
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
window.setFlags( val preference = PreferenceManager.getDefaultSharedPreferences(this)
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS if (Locale.getDefault().language == "ko") {
) if (!preference.getBoolean("https_block_alert", false)) {
android.app.AlertDialog.Builder(this).apply {
setTitle(R.string.https_block_alert_title)
setMessage(R.string.https_block_alert)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
preference.edit().putBoolean("https_block_alert", true).apply()
}
}
with(application as Pupil) {
this@MainActivity.histories = histories
this@MainActivity.downloads = downloads
}
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
checkUpdate() checkUpdate()
main_appbar_layout.addOnOffsetChangedListener( initView()
AppBarLayout.OnOffsetChangedListener { _, p1 ->
main_searchview.translationY = p1.toFloat()
main_recyclerview.translationY = p1.toFloat()
}
)
with(main_swipe_layout) {
setProgressViewOffset(
false,
resources.getDimensionPixelSize(R.dimen.progress_view_start),
resources.getDimensionPixelSize(R.dimen.progress_view_offset)
)
setOnRefreshListener {
CoroutineScope(Dispatchers.Main).launch {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
}
main_nav_view.setNavigationItemSelectedListener {
CoroutineScope(Dispatchers.Main).launch {
main_drawer_layout.closeDrawers()
when(it.itemId) {
R.id.main_drawer_home -> {
cancelFetch()
clearGalleries()
query = query.replace("HISTORY", "")
fetchGalleries(query)
}
R.id.main_drawer_history -> {
cancelFetch()
clearGalleries()
query += "HISTORY"
fetchGalleries(query)
}
R.id.main_drawer_help -> {
AlertDialog.Builder(this@MainActivity).apply {
title = getString(R.string.help_dialog_title)
setMessage(R.string.help_dialog_message)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
}
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))))
}
}
loadBlocks()
}
true
}
setupSearchBar()
setupRecyclerView()
fetchGalleries(query)
loadBlocks()
} }
override fun onBackPressed() { override fun onBackPressed() {
if (main_drawer_layout.isDrawerOpen(GravityCompat.START)) when {
main_drawer_layout.closeDrawer(GravityCompat.START) main_drawer_layout.isDrawerOpen(GravityCompat.START) -> main_drawer_layout.closeDrawer(GravityCompat.START)
else if (query.isNotEmpty()) { query.isNotEmpty() -> runOnUiThread {
runOnUiThread {
query = "" query = ""
findViewById<SearchInputView>(R.id.search_bar_text).setText(query, TextView.BufferType.EDITABLE) findViewById<SearchInputView>(R.id.search_bar_text).setText(query, TextView.BufferType.EDITABLE)
@@ -148,9 +106,8 @@ class MainActivity : AppCompatActivity() {
fetchGalleries(query) fetchGalleries(query)
loadBlocks() loadBlocks()
} }
else -> super.onBackPressed()
} }
else
super.onBackPressed()
} }
override fun onResume() { override fun onResume() {
@@ -165,6 +122,44 @@ class MainActivity : AppCompatActivity() {
super.onResume() super.onResume()
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")!!.toInt()
val maxPage = Math.ceil(totalItems / perPage.toDouble()).roundToInt()
return when(keyCode) {
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (currentPage < maxPage) {
runOnUiThread {
currentPage++
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
true
}
KeyEvent.KEYCODE_VOLUME_UP -> {
if (currentPage > 0) {
runOnUiThread {
currentPage--
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
true
}
else -> super.onKeyDown(keyCode, event)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when(requestCode) { when(requestCode) {
@@ -243,11 +238,81 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun initView() {
var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, p1 ->
main_searchview.translationY = p1.toFloat()
main_recyclerview.scrollBy(0, prevP1 - p1)
prevP1 = p1
}
)
//NavigationView
main_nav_view.setNavigationItemSelectedListener {
runOnUiThread {
main_drawer_layout.closeDrawers()
when(it.itemId) {
R.id.main_drawer_home -> {
cancelFetch()
clearGalleries()
query = ""
mode = Mode.SEARCH
fetchGalleries(query)
loadBlocks()
}
R.id.main_drawer_history -> {
cancelFetch()
clearGalleries()
query = ""
mode = Mode.HISTORY
fetchGalleries(query)
loadBlocks()
}
R.id.main_drawer_downloads -> {
cancelFetch()
clearGalleries()
query = ""
mode = Mode.DOWNLOAD
fetchGalleries(query)
loadBlocks()
}
R.id.main_drawer_help -> {
AlertDialog.Builder(this@MainActivity).apply {
title = getString(R.string.help_dialog_title)
setMessage(R.string.help_dialog_message)
setPositiveButton(android.R.string.ok) { _, _ -> }
}.show()
}
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))))
}
}
}
true
}
setupSearchBar()
setupRecyclerView()
fetchGalleries(query)
loadBlocks()
}
private fun setupRecyclerView() { private fun setupRecyclerView() {
with(main_recyclerview) { with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply { adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add { onChipClickedHandler.add {
post { runOnUiThread {
query = it.toQuery() query = it.toQuery()
this@MainActivity.findViewById<SearchInputView>(R.id.search_bar_text) this@MainActivity.findViewById<SearchInputView>(R.id.search_bar_text)
.setText(query, TextView.BufferType.EDITABLE) .setText(query, TextView.BufferType.EDITABLE)
@@ -259,21 +324,12 @@ class MainActivity : AppCompatActivity() {
} }
} }
} }
addOnScrollListener(
object: RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManager = recyclerView.layoutManager as LinearLayoutManager
if (loadingJob?.isActive != true)
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
loadBlocks()
}
}
)
ItemClickSupport.addTo(this) ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ -> .setOnItemClickListener { _, position, v ->
if (v !is CardView)
return@setOnItemClickListener
val intent = Intent(this@MainActivity, ReaderActivity::class.java) val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery = galleries[position].first val gallery = galleries[position].first
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery)) intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
@@ -281,8 +337,12 @@ class MainActivity : AppCompatActivity() {
//TODO: Maybe sprinke some transitions will be nice :D //TODO: Maybe sprinke some transitions will be nice :D
startActivity(intent) startActivity(intent)
Histories.default.add(gallery.id) histories.add(gallery.id)
}.setOnItemLongClickListener { recyclerView, position, v -> }.setOnItemLongClickListener { recyclerView, position, v ->
if (v !is CardView)
return@setOnItemLongClickListener true
val galleryBlock = galleries[position].first val galleryBlock = galleries[position].first
val view = LayoutInflater.from(this@MainActivity) val view = LayoutInflater.from(this@MainActivity)
.inflate(R.layout.dialog_galleryblock, recyclerView, false) .inflate(R.layout.dialog_galleryblock, recyclerView, false)
@@ -301,7 +361,7 @@ class MainActivity : AppCompatActivity() {
val downloader = GalleryDownloader.get(galleryBlock.id) val downloader = GalleryDownloader.get(galleryBlock.id)
if (downloader == null) { if (downloader == null) {
GalleryDownloader(context, galleryBlock, true).start() GalleryDownloader(context, galleryBlock, true).start()
Histories.default.add(galleryBlock.id) downloads.add(galleryBlock.id)
} else { } else {
downloader.cancel() downloader.cancel()
downloader.clearNotification() downloader.clearNotification()
@@ -329,6 +389,209 @@ class MainActivity : AppCompatActivity() {
true true
} }
var origin = 0f
var target = -1
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val perPage = preferences.getString("per_page", "25")!!.toInt()
setOnTouchListener { _, event ->
when(event.action) {
MotionEvent.ACTION_UP -> {
origin = 0f
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(showPrev) {
showPrev = false
val prev = main_recyclerview.layoutManager?.getChildAt(0)
if (prev is LinearLayout) {
val icon = prev.findViewById<ImageView>(R.id.icon_prev)
prev.layoutParams.height = 1
icon.layoutParams.height = 1
icon.rotation = 180f
}
prev?.requestLayout()
notifyItemRemoved(0)
}
if(showNext) {
showNext = false
val next = main_recyclerview.layoutManager?.let {
getChildAt(childCount-1)
}
if (next is LinearLayout) {
val icon = next.findViewById<ImageView>(R.id.icon_next)
next.layoutParams.height = 1
icon.layoutParams.height = 1
icon.rotation = 0f
}
next?.requestLayout()
notifyItemRemoved(itemCount)
}
}
if (target != -1) {
currentPage = target
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
target = -1
}
}
MotionEvent.ACTION_DOWN -> origin = event.y
MotionEvent.ACTION_MOVE -> {
if (origin == 0f)
origin = event.y
val dist = event.y - origin
when {
!canScrollVertically(-1) -> {
//TOP
//Scrolling UP
if (dist > 0 && currentPage != 0) {
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(!showPrev) {
showPrev = true
notifyItemInserted(0)
}
}
val prev = main_recyclerview.layoutManager?.getChildAt(0)
if (prev is LinearLayout) {
val icon = prev.findViewById<ImageView>(R.id.icon_prev)
val text = prev.findViewById<TextView>(R.id.text_prev).apply {
text = getString(R.string.main_move, currentPage)
}
if (dist < 360) {
prev.layoutParams.height = (dist/2).roundToInt()
icon.layoutParams.height = (dist/2).roundToInt()
icon.rotation = dist+180
text.layoutParams.width = dist.roundToInt()
target = -1
}
else {
prev.layoutParams.height = 180
icon.layoutParams.height = 180
icon.rotation = 180f
text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT
target = currentPage-1
}
}
prev?.requestLayout()
return@setOnTouchListener true
} else {
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(showPrev) {
showPrev = false
val prev = main_recyclerview.layoutManager?.getChildAt(0)
if (prev is LinearLayout) {
val icon = prev.findViewById<ImageView>(R.id.icon_prev)
prev.layoutParams.height = 1
icon.layoutParams.height = 1
icon.rotation = 180f
}
prev?.requestLayout()
notifyItemRemoved(0)
}
}
}
}
!canScrollVertically(1) -> {
//BOTTOM
//Scrolling DOWN
if (dist < 0 && currentPage != Math.ceil(totalItems.toDouble()/perPage).roundToInt()-1) {
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(!showNext) {
showNext = true
notifyItemInserted(itemCount-1)
}
}
val next = main_recyclerview.layoutManager?.let {
getChildAt(childCount-1)
}
val absDist = Math.abs(dist)
if (next is LinearLayout) {
val icon = next.findViewById<ImageView>(R.id.icon_next)
val text = next.findViewById<TextView>(R.id.text_next).apply {
text = getString(R.string.main_move, currentPage+2)
}
if (absDist < 360) {
next.layoutParams.height = (absDist/2).roundToInt()
icon.layoutParams.height = (absDist/2).roundToInt()
icon.rotation = -absDist
text.layoutParams.width = absDist.roundToInt()
target = -1
}
else {
next.layoutParams.height = 180
icon.layoutParams.height = 180
icon.rotation = 0f
text.layoutParams.width = LinearLayout.LayoutParams.WRAP_CONTENT
target = currentPage+1
}
}
next?.requestLayout()
return@setOnTouchListener true
} else {
with(main_recyclerview.adapter as GalleryBlockAdapter) {
if(showNext) {
showNext = false
val next = main_recyclerview.layoutManager?.let {
getChildAt(childCount-1)
}
if (next is LinearLayout) {
val icon = next.findViewById<ImageView>(R.id.icon_next)
next.layoutParams.height = 1
icon.layoutParams.height = 1
icon.rotation = 180f
}
next?.requestLayout()
notifyItemRemoved(itemCount)
}
}
}
}
}
}
}
false
}
} }
} }
@@ -357,7 +620,33 @@ class MainActivity : AppCompatActivity() {
setOnMenuItemClickListener { setOnMenuItemClickListener {
when(it.itemId) { when(it.itemId) {
R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), SETTINGS) R.id.main_menu_settings -> startActivityForResult(Intent(this@MainActivity, SettingsActivity::class.java), SETTINGS)
R.id.main_menu_search -> setSearchFocused(true) R.id.main_menu_page_indicator -> {
val preference = PreferenceManager.getDefaultSharedPreferences(context)
val perPage = preference.getString("per_page", "25")!!.toInt()
val editText = EditText(context)
AlertDialog.Builder(context).apply {
title = getString(R.string.reader_go_to_page)
setView(editText)
setTitle(R.string.main_jump_title)
setMessage(getString(
R.string.main_jump_message,
currentPage+1,
Math.ceil(totalItems / perPage.toDouble()).roundToInt()
))
setPositiveButton(android.R.string.ok) { _, _ ->
currentPage = (editText.text.toString().toIntOrNull() ?: return@setPositiveButton)-1
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
}.show()
}
} }
} }
@@ -442,7 +731,7 @@ class MainActivity : AppCompatActivity() {
if (query != this@MainActivity.query) { if (query != this@MainActivity.query) {
this@MainActivity.query = query this@MainActivity.query = query
CoroutineScope(Dispatchers.Main).launch { runOnUiThread {
cancelFetch() cancelFetch()
clearGalleries() clearGalleries()
fetchGalleries(query) fetchGalleries(query)
@@ -473,12 +762,12 @@ class MainActivity : AppCompatActivity() {
this.notifyDataSetChanged() this.notifyDataSetChanged()
} }
main_appbar_layout.setExpanded(true)
main_noresult.visibility = View.INVISIBLE main_noresult.visibility = View.INVISIBLE
main_progressbar.show() main_progressbar.show()
main_swipe_layout.isRefreshing = false
} }
private fun fetchGalleries(query: String, from: Int = 0) { private fun fetchGalleries(query: String) {
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25 val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!! val defaultQuery = preference.getString("default_query", "")!!
@@ -489,13 +778,48 @@ class MainActivity : AppCompatActivity() {
return return
galleryIDs = CoroutineScope(Dispatchers.IO).async { galleryIDs = CoroutineScope(Dispatchers.IO).async {
when { when(mode) {
query.contains("HISTORY") -> Mode.SEARCH -> {
Histories.default.toList() when {
query.isEmpty() and defaultQuery.isEmpty() -> query.isEmpty() and defaultQuery.isEmpty() -> {
fetchNozomi(start = from, count = perPage) fetchNozomi(start = currentPage*perPage, count = perPage).let {
else -> totalItems = it.second
doSearch("$defaultQuery $query") it.first
}
}
else -> doSearch("$defaultQuery $query").apply {
totalItems = size
}
}
}
Mode.HISTORY -> {
when {
query.isEmpty() -> {
histories.toList().apply {
totalItems = size
}
}
else -> {
val result = doSearch(query).sorted()
histories.filter { result.binarySearch(it) >= 0 }.apply {
totalItems = size
}
}
}
}
Mode.DOWNLOAD -> {
when {
query.isEmpty() -> downloads.toList().apply {
totalItems = size
}
else -> {
val result = doSearch(query).sorted()
downloads.filter { result.binarySearch(it) >= 0 }.apply {
totalItems = size
}
}
}
}
} }
} }
} }
@@ -517,18 +841,11 @@ class MainActivity : AppCompatActivity() {
return@launch 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 { when {
query.isEmpty() and defaultQuery.isEmpty() -> query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
galleryIDs galleryIDs
else -> else ->
galleryIDs.slice(galleries.size until Math.min(galleries.size+perPage, galleryIDs.size)) galleryIDs.slice(currentPage*perPage until Math.min(currentPage*perPage+perPage, galleryIDs.size))
}.chunked(5).let { chunks -> }.chunked(5).let { chunks ->
for (chunk in chunks) for (chunk in chunks)
chunk.map { chunk.map {
@@ -586,8 +903,7 @@ class MainActivity : AppCompatActivity() {
if (galleryBlock != null) { if (galleryBlock != null) {
galleries.add(galleryBlock) galleries.add(galleryBlock)
main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
} }
} }
} }

View File

@@ -6,17 +6,23 @@ import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build
import android.preference.PreferenceManager import android.preference.PreferenceManager
import android.util.SparseArray import androidx.core.content.ContextCompat
import com.finotes.android.finotescore.Fn import com.finotes.android.finotescore.Fn
import com.finotes.android.finotescore.ObservableApplication import com.finotes.android.finotescore.ObservableApplication
import com.finotes.android.finotescore.Severity import xyz.quaver.pupil.util.Histories
import kotlinx.coroutines.Job import java.io.File
class Pupil : ObservableApplication() { class Pupil : ObservableApplication() {
lateinit var histories: Histories
lateinit var downloads: Histories
override fun onCreate() { override fun onCreate() {
val preference = PreferenceManager.getDefaultSharedPreferences(this) val preference = PreferenceManager.getDefaultSharedPreferences(this)
histories = Histories(File(ContextCompat.getDataDir(this), "histories.json"))
downloads = Histories(File(ContextCompat.getDataDir(this), "downloads.json"))
super.onCreate() super.onCreate()
Fn.init(this) Fn.init(this)

View File

@@ -2,7 +2,6 @@ package xyz.quaver.pupil
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.* import android.view.*
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -13,12 +12,14 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_reader.* import kotlinx.android.synthetic.main.activity_reader.*
import kotlinx.android.synthetic.main.activity_reader.view.* import kotlinx.android.synthetic.main.activity_reader.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.* import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.io.IOException
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock import xyz.quaver.hitomi.GalleryBlock
@@ -63,7 +64,7 @@ class ReaderActivity : AppCompatActivity() {
initView() initView()
if (!downloader.notify) if (!downloader.download)
downloader.start() downloader.start()
} }
@@ -90,7 +91,7 @@ class ReaderActivity : AppCompatActivity() {
when(item?.itemId) { when(item?.itemId) {
R.id.reader_menu_page_indicator -> { R.id.reader_menu_page_indicator -> {
val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false) val view = LayoutInflater.from(this).inflate(R.layout.dialog_numberpicker, findViewById(android.R.id.content), false)
with(view.reader_dialog_number_picker) { with(view.dialog_number_picker) {
minValue=1 minValue=1
maxValue=gallerySize maxValue=gallerySize
value=currentPage value=currentPage
@@ -98,8 +99,8 @@ class ReaderActivity : AppCompatActivity() {
val dialog = AlertDialog.Builder(this).apply { val dialog = AlertDialog.Builder(this).apply {
setView(view) setView(view)
}.create() }.create()
view.reader_dialog_ok.setOnClickListener { view.dialog_ok.setOnClickListener {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.reader_dialog_number_picker.value-1, 0) (reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.dialog_number_picker.value-1, 0)
dialog.dismiss() dialog.dismiss()
} }
@@ -113,7 +114,7 @@ class ReaderActivity : AppCompatActivity() {
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (!downloader.notify) if (!downloader.download)
downloader.cancel() downloader.cancel()
} }
@@ -136,7 +137,13 @@ class ReaderActivity : AppCompatActivity() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id) var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
if (d == null) { if (d == null) {
d = GalleryDownloader(this, galleryBlock) try {
d = GalleryDownloader(this, galleryBlock)
} catch (e: IOException) {
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
finish()
return
}
} }
downloader = d.apply { downloader = d.apply {
@@ -159,18 +166,21 @@ class ReaderActivity : AppCompatActivity() {
} }
} }
onDownloadedHandler = { onDownloadedHandler = {
val item = it.toList()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
if (images.isEmpty()) { if (images.isEmpty()) {
images.addAll(it) images.addAll(item)
reader_recyclerview.adapter?.notifyDataSetChanged() reader_recyclerview.adapter?.notifyDataSetChanged()
} else { } else {
images.add(it.last()) images.add(item.last())
reader_recyclerview.adapter?.notifyItemInserted(images.size-1) reader_recyclerview.adapter?.notifyItemInserted(images.size-1)
} }
} }
} }
onErrorHandler = { onErrorHandler = {
downloader.notify = false if (it is IOException)
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
downloader.download = false
} }
onCompleteHandler = { onCompleteHandler = {
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Main).launch {
@@ -180,32 +190,36 @@ class ReaderActivity : AppCompatActivity() {
onNotifyChangedHandler = { notify -> onNotifyChangedHandler = { notify ->
val fab = reader_fab_download val fab = reader_fab_download
if (notify) { runOnUiThread {
val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading) if (notify) {
icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() { val icon = AnimatedVectorDrawableCompat.create(this, R.drawable.ic_downloading)
override fun onAnimationEnd(drawable: Drawable?) { icon?.registerAnimationCallback(object: Animatable2Compat.AnimationCallback() {
if (downloader.notify) override fun onAnimationEnd(drawable: Drawable?) {
fab.post { if (downloader.download)
icon.start() fab.post {
fab.labelText = getString(R.string.reader_fab_download_cancel) icon.start()
} fab.labelText = getString(R.string.reader_fab_download_cancel)
else }
fab.post { else
fab.setImageResource(R.drawable.ic_download) fab.post {
fab.labelText = getString(R.string.reader_fab_download) fab.setImageResource(R.drawable.ic_download)
} fab.labelText = getString(R.string.reader_fab_download)
} }
}) }
})
fab.setImageDrawable(icon) fab.setImageDrawable(icon)
icon?.start() icon?.start()
} else { } else {
fab.setImageResource(R.drawable.ic_download) runOnUiThread {
fab.setImageResource(R.drawable.ic_download)
}
}
} }
} }
} }
if (downloader.notify) { if (downloader.download) {
downloader.invokeOnReaderLoaded() downloader.invokeOnReaderLoaded()
downloader.invokeOnNotifyChanged() downloader.invokeOnNotifyChanged()
} }
@@ -255,9 +269,9 @@ class ReaderActivity : AppCompatActivity() {
} }
reader_fab_download.setOnClickListener { reader_fab_download.setOnClickListener {
downloader.notify = !downloader.notify downloader.download = !downloader.download
if (!downloader.notify) if (!downloader.download)
downloader.clearNotification() downloader.clearNotification()
} }
} }

View File

@@ -16,7 +16,6 @@ import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import kotlinx.android.synthetic.main.dialog_default_query.view.* import kotlinx.android.synthetic.main.dialog_default_query.view.*
import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Histories
import java.io.File import java.io.File
class SettingsActivity : AppCompatActivity() { class SettingsActivity : AppCompatActivity() {
@@ -99,7 +98,7 @@ class SettingsActivity : AppCompatActivity() {
with(findPreference<Preference>("clear_history")) { with(findPreference<Preference>("clear_history")) {
this ?: return@with this ?: return@with
val histories = Histories.default val histories = (activity!!.application as Pupil).histories
summary = getString(R.string.settings_clear_history_summary, histories.size) summary = getString(R.string.settings_clear_history_summary, histories.size)

View File

@@ -1,14 +1,13 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.PorterDuff
import android.util.Log import android.util.Log
import android.util.SparseArray
import android.util.SparseBooleanArray import android.util.SparseBooleanArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
@@ -29,66 +28,28 @@ import xyz.quaver.pupil.types.Tag
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType { enum class ViewType {
VIEW_ITEM, NEXT,
VIEW_PROG GALLERY,
PREV
} }
private fun String.wordCapitalize() : String { inner class GalleryViewHolder(private val view: CardView) : RecyclerView.ViewHolder(view) {
val result = ArrayList<String>() fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
with(view) {
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
}
var noMore = false
private val refreshTasks = SparseArray<TimerTask>()
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
class ViewHolder(val view: CardView, var galleryID: Int? = null) : RecyclerView.ViewHolder(view)
class ProgressViewHolder(val view: LinearLayout) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
when(viewType) {
ViewType.VIEW_ITEM.ordinal -> {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_galleryblock, parent, false
) as CardView
return ViewHolder(view)
}
ViewType.VIEW_PROG.ordinal -> {
val view = LayoutInflater.from(parent.context).inflate(
R.layout.item_progressbar, parent, false
) as LinearLayout
return ProgressViewHolder(view)
}
}
throw Exception("Unexpected ViewType")
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolder) {
with(holder.view) {
val resources = context.resources val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map { val languages = resources.getStringArray(R.array.languages).map {
it.split("|").let { split -> it.split("|").let { split ->
Pair(split[0], split[1]) Pair(split[0], split[1])
} }
}.toMap() }.toMap()
val (gallery, thumbnail) = galleries[position]
holder.galleryID = gallery.id val (gallery: GalleryBlock, thumbnail: Deferred<String>) = item
val artists = gallery.artists val artists = gallery.artists
val series = gallery.series val series = gallery.series
@@ -101,7 +62,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val bitmap = BitmapFactory.decodeFile(thumbnail.await()) val bitmap = BitmapFactory.decodeFile(thumbnail.await())
CoroutineScope(Dispatchers.Main).launch { post {
galleryblock_thumbnail.setImageBitmap(bitmap) galleryblock_thumbnail.setImageBitmap(bitmap)
} }
} }
@@ -124,10 +85,10 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
galleryblock_progressbar.visibility = View.GONE galleryblock_progressbar.visibility = View.GONE
} }
if (refreshTasks.get(gallery.id) == null) { if (refreshTasks[this@GalleryViewHolder] == null) {
val refresh = Timer(false).schedule(0, 1000) { val refresh = Timer(false).schedule(0, 1000) {
post { post {
with(galleryblock_progressbar) { with(view.galleryblock_progressbar) {
progress = imageCache.list()?.size ?: 0 progress = imageCache.list()?.size ?: 0
if (!readerCache.exists()) { if (!readerCache.exists()) {
@@ -135,7 +96,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
max = 0 max = 0
progress = 0 progress = 0
holder.view.galleryblock_progress_complete.visibility = View.INVISIBLE view.galleryblock_progress_complete.visibility = View.INVISIBLE
} else { } else {
if (visibility == View.GONE) { if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable) val reader = Json(JsonConfiguration.Stable)
@@ -146,24 +107,21 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
if (progress == max) { if (progress == max) {
if (completeFlag.get(gallery.id, false)) { if (completeFlag.get(gallery.id, false)) {
with(holder.view.galleryblock_progress_complete) { with(view.galleryblock_progress_complete) {
setImageResource(R.drawable.ic_progressbar) setImageResource(R.drawable.ic_progressbar)
visibility = View.VISIBLE visibility = View.VISIBLE
} }
} else { } else {
val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete) val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete)
with(holder.view.galleryblock_progress_complete) { with(view.galleryblock_progress_complete) {
setImageDrawable(drawable) setImageDrawable(drawable)
visibility = View.VISIBLE visibility = View.VISIBLE
} }
drawable?.start() drawable?.start()
completeFlag.put(gallery.id, true) completeFlag.put(gallery.id, true)
} }
} else { } else
with(holder.view.galleryblock_progress_complete) { view.galleryblock_progress_complete.visibility = View.INVISIBLE
visibility = View.INVISIBLE
}
}
null null
} }
@@ -171,7 +129,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
} }
} }
refreshTasks.put(gallery.id, refresh) refreshTasks[this@GalleryViewHolder] = refresh
} }
galleryblock_title.text = gallery.title galleryblock_title.text = gallery.title
@@ -207,7 +165,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val tag = Tag.parse(it) val tag = Tag.parse(it)
val chip = LayoutInflater.from(context) val chip = LayoutInflater.from(context)
.inflate(R.layout.tag_chip, holder.view, false) as Chip .inflate(R.layout.tag_chip, this, false) as Chip
val icon = when(tag.area) { val icon = when(tag.area) {
"male" -> { "male" -> {
@@ -234,32 +192,85 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
} }
} }
} }
if (holder is ProgressViewHolder) { }
holder.view.visibility = when(noMore) { class NextViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
true -> View.GONE class PrevViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
false -> View.VISIBLE
class ViewHolderFactory {
companion object {
fun getLayoutID(type: Int): Int {
return when(ViewType.values()[type]) {
ViewType.NEXT -> R.layout.item_next
ViewType.PREV -> R.layout.item_prev
ViewType.GALLERY -> R.layout.item_galleryblock
}
} }
} }
} }
private fun String.wordCapitalize() : String {
val result = ArrayList<String>()
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
}
private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
val completeFlag = SparseBooleanArray()
val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
var showNext = false
var showPrev = false
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
fun getViewHolder(type: Int, view: View): RecyclerView.ViewHolder {
return when(ViewType.values()[type]) {
ViewType.NEXT -> NextViewHolder(view as LinearLayout)
ViewType.PREV -> PrevViewHolder(view as LinearLayout)
ViewType.GALLERY -> GalleryViewHolder(view as CardView)
}
}
return getViewHolder(
viewType,
LayoutInflater.from(parent.context).inflate(
ViewHolderFactory.getLayoutID(viewType),
parent,
false
)
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GalleryViewHolder)
holder.bind(galleries[position-(if (showPrev) 1 else 0)])
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) { override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder) super.onViewDetachedFromWindow(holder)
if (holder is ViewHolder) { if (holder is GalleryViewHolder) {
val galleryID = holder.galleryID ?: return val task = refreshTasks[holder] ?: return
val task = refreshTasks.get(galleryID) ?: return
refreshTasks.remove(galleryID)
task.cancel() task.cancel()
refreshTasks.remove(holder)
} }
} }
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+1 override fun getItemCount() =
(if (galleries.isEmpty()) 0 else galleries.size)+
(if (showNext) 1 else 0)+
(if (showPrev) 1 else 0)
override fun getItemViewType(position: Int): Int { override fun getItemViewType(position: Int): Int {
return when { return when {
galleries.getOrNull(position) == null -> ViewType.VIEW_PROG.ordinal showPrev && position == 0 -> ViewType.PREV
else -> ViewType.VIEW_ITEM.ordinal showNext && position == galleries.size+(if (showPrev) 1 else 0) -> ViewType.NEXT
} else -> ViewType.GALLERY
}.ordinal
} }
} }

View File

@@ -38,9 +38,7 @@ class ReaderAdapter(private val images: List<String>) : RecyclerView.Adapter<Rea
val image = BitmapFactory.decodeFile(images[position], options) val image = BitmapFactory.decodeFile(images[position], options)
launch(Dispatchers.Main) { post { setImageBitmap(image) }
setImageBitmap(image)
}
} }
} }
} }

View File

@@ -31,7 +31,7 @@ class GalleryDownloader(
_notify: Boolean = false _notify: Boolean = false
) : ContextWrapper(base) { ) : ContextWrapper(base) {
var notify: Boolean = false var download: Boolean = false
set(value) { set(value) {
if (value) { if (value) {
field = true field = true
@@ -55,7 +55,7 @@ class GalleryDownloader(
var onReaderLoadedHandler: ((Reader) -> Unit)? = null var onReaderLoadedHandler: ((Reader) -> Unit)? = null
var onProgressHandler: ((Int) -> Unit)? = null var onProgressHandler: ((Int) -> Unit)? = null
var onDownloadedHandler: ((List<String>) -> Unit)? = null var onDownloadedHandler: ((List<String>) -> Unit)? = null
var onErrorHandler: (() -> Unit)? = null var onErrorHandler: ((Exception) -> Unit)? = null
var onCompleteHandler: (() -> Unit)? = null var onCompleteHandler: (() -> Unit)? = null
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
@@ -67,7 +67,7 @@ class GalleryDownloader(
initNotification() initNotification()
reader = CoroutineScope(Dispatchers.IO).async { reader = CoroutineScope(Dispatchers.IO).async {
notify = _notify download = _notify
val json = Json(JsonConfiguration.Stable) val json = Json(JsonConfiguration.Stable)
val serializer = ReaderItem.serializer().list val serializer = ReaderItem.serializer().list
val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader) val preference = PreferenceManager.getDefaultSharedPreferences(this@GalleryDownloader)
@@ -100,15 +100,13 @@ class GalleryDownloader(
} }
} }
//Could not retrieve reader if (reader.isNotEmpty()) {
if (reader.isEmpty()) //Save cache
throw IOException("Can't retrieve Reader") if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
//Save cache cache.writeText(json.stringify(serializer, reader))
if (!cache.parentFile.exists()) }
cache.parentFile.mkdirs()
cache.writeText(json.stringify(serializer, reader))
reader reader
} }
@@ -120,6 +118,9 @@ class GalleryDownloader(
downloadJob = CoroutineScope(Dispatchers.Default).launch { downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader.await() val reader = reader.await()
if (reader.isEmpty())
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
val list = ArrayList<String>() val list = ArrayList<String>()
onReaderLoadedHandler?.invoke(reader) onReaderLoadedHandler?.invoke(reader)
@@ -138,7 +139,7 @@ class GalleryDownloader(
.setProgress(reader.size, index, false) .setProgress(reader.size, index, false)
.setContentText("$index/${reader.size}") .setContentText("$index/${reader.size}")
if (notify) if (download)
notificationManager.notify(galleryBlock.id, notificationBuilder.build()) notificationManager.notify(galleryBlock.id, notificationBuilder.build())
async(Dispatchers.IO) { async(Dispatchers.IO) {
@@ -162,7 +163,7 @@ class GalleryDownloader(
} catch (e: Exception) { } catch (e: Exception) {
cache.delete() cache.delete()
onErrorHandler?.invoke() onErrorHandler?.invoke(e)
notificationBuilder notificationBuilder
.setContentTitle(galleryBlock.title) .setContentTitle(galleryBlock.title)
@@ -188,10 +189,10 @@ class GalleryDownloader(
.setContentText(getString(R.string.reader_notification_complete)) .setContentText(getString(R.string.reader_notification_complete))
.setProgress(0, 0, false) .setProgress(0, 0, false)
if (notify) if (download)
notificationManager.notify(galleryBlock.id, notificationBuilder.build()) notificationManager.notify(galleryBlock.id, notificationBuilder.build())
notify = false download = false
} }
remove(galleryBlock.id) remove(galleryBlock.id)
@@ -219,7 +220,7 @@ class GalleryDownloader(
} }
fun invokeOnNotifyChanged() { fun invokeOnNotifyChanged() {
onNotifyChangedHandler?.invoke(notify) onNotifyChangedHandler?.invoke(download)
} }
private fun initNotification() { private fun initNotification() {

View File

@@ -20,10 +20,6 @@ class Histories(private val file: File) : ArrayList<Int>() {
} }
} }
companion object {
lateinit var default: Histories
}
@UseExperimental(ImplicitReflectionSerializer::class) @UseExperimental(ImplicitReflectionSerializer::class)
fun load() : Histories { fun load() : Histories {
return apply { return apply {

View File

@@ -1,6 +1,5 @@
package xyz.quaver.pupil.util package xyz.quaver.pupil.util
import kotlinx.io.IOException
import kotlinx.serialization.json.* import kotlinx.serialization.json.*
import java.net.URL import java.net.URL

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#fff"
android:pathData="M18.4,10.6C16.55,8.99 14.15,8 11.5,8c-4.65,0 -8.58,3.03 -9.96,7.22L3.9,16c1.05,-3.19 4.05,-5.5 7.6,-5.5 1.95,0 3.73,0.72 5.12,1.88L13,16h9V7l-3.6,3.6z"/>
<path
android:fillColor="#fff"
android:pathData="M8.5,15
a1.5,1.5 0 1,1 1.5,1.5
a1.5,1.5 0 0,1 -1.5,-1.5 Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M15.41,7.41L14,6l-6,6 6,6 1.41,-1.41L10.83,12z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M10,6L8.59,7.41 13.17,12l-4.58,4.59L10,18l6,-6z"/>
</vector>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout <RelativeLayout
android:id="@+id/main_layout"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
@@ -22,7 +23,7 @@
<View <View
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="80dp" android:layout_height="64dp"
android:visibility="invisible" android:visibility="invisible"
android:background="@color/transparent" android:background="@color/transparent"
app:layout_scrollFlags="scroll|enterAlways" app:layout_scrollFlags="scroll|enterAlways"
@@ -46,22 +47,14 @@
android:text="@string/main_no_result" android:text="@string/main_no_result"
android:visibility="invisible"/> android:visibility="invisible"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.recyclerview.widget.RecyclerView
android:id="@+id/main_swipe_layout" android:id="@+id/main_recyclerview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="-80dp"> android:paddingTop="64dp"
android:clipToPadding="false"
<androidx.recyclerview.widget.RecyclerView android:scrollbars="vertical"
android:id="@+id/main_recyclerview" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="80dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -71,7 +64,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:floatingSearch_searchBarMarginLeft="8dp" app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp" app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="24dp" app:floatingSearch_searchBarMarginTop="8dp"
app:floatingSearch_searchHint="@string/search_hint" app:floatingSearch_searchHint="@string/search_hint"
app:floatingSearch_suggestionsListAnimDuration="250" app:floatingSearch_suggestionsListAnimDuration="250"
app:floatingSearch_showSearchKey="true" app:floatingSearch_showSearchKey="true"

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/reader_layout"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -39,6 +40,7 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="16dp"
app:menu_colorNormal="@color/colorAccent"> app:menu_colorNormal="@color/colorAccent">
<com.github.clans.fab.FloatingActionButton <com.github.clans.fab.FloatingActionButton
@@ -46,7 +48,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/ic_downloading" android:src="@drawable/ic_downloading"
android:tint="@android:color/white"
app:fab_label="@string/reader_fab_download" app:fab_label="@string/reader_fab_download"
app:fab_size="mini"/> app:fab_size="mini"/>

View File

@@ -7,7 +7,7 @@
<TextView <TextView
style="?android:textAppearanceLarge" style="?android:textAppearanceLarge"
android:id="@+id/reader_dialog_title" android:id="@+id/dialog_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/reader_go_to_page" android:text="@string/reader_go_to_page"
@@ -15,20 +15,20 @@
app:layout_constraintStart_toStartOf="parent"/> app:layout_constraintStart_toStartOf="parent"/>
<NumberPicker <NumberPicker
android:id="@+id/reader_dialog_number_picker" android:id="@+id/dialog_number_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/reader_dialog_title" app:layout_constraintTop_toBottomOf="@id/dialog_title"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
<Button <Button
android:id="@+id/reader_dialog_ok" android:id="@+id/dialog_ok"
style="?borderlessButtonStyle" style="?borderlessButtonStyle"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@android:string/ok" android:text="@android:string/ok"
app:layout_constraintTop_toBottomOf="@id/reader_dialog_number_picker" app:layout_constraintTop_toBottomOf="@id/dialog_number_picker"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<ImageView
android:id="@+id/icon_next"
android:contentDescription="@string/page_indicator_placeholder"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_navigate_next_black_24dp"
android:tint="@color/colorAccent"
android:rotation="180"/>
<TextView
android:id="@+id/text_next"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
<ImageView
android:id="@+id/icon_prev"
android:contentDescription="@string/page_indicator_placeholder"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:src="@drawable/ic_navigate_next_black_24dp"
android:tint="@color/colorAccent"
android:rotation="180"/>
<TextView
android:id="@+id/text_prev"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:maxLines="1"
android:ellipsize="end" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"/>
</LinearLayout>

View File

@@ -10,6 +10,10 @@
<item android:id="@+id/main_drawer_history" <item android:id="@+id/main_drawer_history"
android:title="@string/main_drawer_history" android:title="@string/main_drawer_history"
android:icon="@drawable/ic_history"/> android:icon="@drawable/ic_history"/>
<item android:id="@+id/main_drawer_downloads"
android:title="@string/main_drawer_downloads"
android:icon="@drawable/ic_download"/>
</group> </group>
<item android:title="@string/main_drawer_group_contact_title"> <item android:title="@string/main_drawer_group_contact_title">

View File

@@ -2,10 +2,9 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item android:id="@+id/main_menu_page_indicator"
android:id="@+id/main_menu_search" android:icon="@drawable/ic_jump"
android:icon="@drawable/ic_search" android:title="@string/page_indicator_placeholder"
android:title="@string/main_search"
app:showAsAction="ifRoom"/> app:showAsAction="ifRoom"/>
<item <item

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/reader_menu_page_indicator" <item android:id="@+id/reader_menu_page_indicator"
android:title="@string/reader_page_indicator_placeholder" android:title="@string/page_indicator_placeholder"
app:showAsAction="always|withText"/> app:showAsAction="always|withText"/>
</menu> </menu>

View File

@@ -6,12 +6,12 @@
<string name="main_no_result">結果なし</string> <string name="main_no_result">結果なし</string>
<string name="main_search">検索</string> <string name="main_search">検索</string>
<string name="search_hint">ギャラリー検索</string> <string name="search_hint">ギャラリー検索</string>
<string name="search_hint_with_page">ギャラリー検索</string>
<string name="settings_cache_title">キャッシュ</string> <string name="settings_cache_title">キャッシュ</string>
<string name="settings_clear_image_cache">イメージキャッシュクリア</string> <string name="settings_clear_image_cache">イメージキャッシュクリア</string>
<string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string> <string name="settings_clear_cache_alert_message">キャッシュをクリアするとイメージのロード速度に影響を与えます。実行しますか?</string>
<string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string> <string name="settings_clear_cache_summary">キャッシュサイズ: %1$d%2$s</string>
<string name="settings_default_query">デフォルトキーワード</string> <string name="settings_default_query">デフォルトキーワード</string>
<string name="permission_explain">権限を拒否すると一部の機能が利用できません</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string> <string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string> <string name="settings_search_title">検索設定</string>
<string name="settings_title">設定</string> <string name="settings_title">設定</string>
@@ -52,4 +52,11 @@
<string name="reader_notification_error">ダウンロードエラー</string> <string name="reader_notification_error">ダウンロードエラー</string>
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string> <string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</string>
<string name="main_dialog_delete">このギャラリーを削除</string> <string name="main_dialog_delete">このギャラリーを削除</string>
<string name="main_drawer_downloads">ダウンロード</string>
<string name="main_jump_title">ページ移動</string>
<string name="main_jump_message">現ページ番号: %1$d\nページ数: %2$d</string>
<string name="unable_to_connect">hitomi.laに接続できません</string>
<string name="main_move">%1$dページへ移動</string>
<string name="https_block_alert_title">(Korean only)</string>
<string name="https_block_alert">(Korean only)</string>
</resources> </resources>

View File

@@ -3,8 +3,8 @@
<string name="galleryblock_language">언어: %1$s</string> <string name="galleryblock_language">언어: %1$s</string>
<string name="galleryblock_series">시리즈: %1$s</string> <string name="galleryblock_series">시리즈: %1$s</string>
<string name="galleryblock_type">종류: %1$s</string> <string name="galleryblock_type">종류: %1$s</string>
<string name="permission_explain">권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다</string>
<string name="search_hint">갤러리 검색</string> <string name="search_hint">갤러리 검색</string>
<string name="search_hint_with_page">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string> <string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string> <string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string> <string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
@@ -52,4 +52,11 @@
<string name="reader_notification_error">다운로드 오류</string> <string name="reader_notification_error">다운로드 오류</string>
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string> <string name="reader_fab_download_cancel">백그라운드 다운로드 취소</string>
<string name="main_dialog_delete">갤러리 삭제</string> <string name="main_dialog_delete">갤러리 삭제</string>
<string name="main_drawer_downloads">다운로드</string>
<string name="main_jump_title">페이지 이동</string>
<string name="main_jump_message">현재 페이지: %1$d\n페이지 수: %2$d</string>
<string name="unable_to_connect">hitomi.la에 연결할 수 없습니다</string>
<string name="main_move">%1$d 페이지로 이동</string>
<string name="https_block_alert_title">접속 불가 현상 안내</string>
<string name="https_block_alert">최근 https 차단으로 접속이 안 되는 경우가 발생하고 있습니다\n이 경우 플레이스토어에서 SNIper앱을 이용하시면 정상이용이 가능합니다.</string>
</resources> </resources>

View File

@@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
<item name="android:windowLightStatusBar">true</item>
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>

View File

@@ -12,7 +12,7 @@
<string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string> <string name="galleryblock_thumbnail_description" translatable="false">Thumbnail</string>
<string name="reader_imageview_description" translatable="false">Content ImageView</string> <string name="reader_imageview_description" translatable="false">Content ImageView</string>
<string name="reader_page_indicator_placeholder" translatable="false">-/-</string> <string name="page_indicator_placeholder" translatable="false">-/-</string>
<string name="plus_to_close" translatable="false">Fab</string> <string name="plus_to_close" translatable="false">Fab</string>
@@ -20,22 +20,31 @@
<string name="warning">Warning</string> <string name="warning">Warning</string>
<string name="permission_explain">Denying any permission can deactivate some functions</string> <string name="https_block_alert_title">(Korean only)</string>
<string name="https_block_alert">(Korean only)</string>
<string name="channel_download">Download</string> <string name="channel_download">Download</string>
<string name="channel_download_description">Shows download status</string> <string name="channel_download_description">Shows download status</string>
<string name="unable_to_connect">Unable to connect to hitomi.la</string>
<string name="main_search">Search</string> <string name="main_search">Search</string>
<string name="main_no_result">No result</string> <string name="main_no_result">No result</string>
<string name="main_drawer_home">Home</string> <string name="main_drawer_home">Home</string>
<string name="main_drawer_history">History</string> <string name="main_drawer_history">History</string>
<string name="main_drawer_downloads">Downloads</string>
<string name="main_drawer_group_contact_title">Contact</string> <string name="main_drawer_group_contact_title">Contact</string>
<string name="main_drawer_group_contact_help">Help</string> <string name="main_drawer_group_contact_help">Help</string>
<string name="main_drawer_group_contact_homepage">Visit homepage</string> <string name="main_drawer_group_contact_homepage">Visit homepage</string>
<string name="main_drawer_group_contact_github">Visit github</string> <string name="main_drawer_group_contact_github">Visit github</string>
<string name="main_drawer_group_contact_email">Email me!</string> <string name="main_drawer_group_contact_email">Email me!</string>
<string name="main_jump_title">Jump to page</string>
<string name="main_jump_message">Current page: %1$d\nMaximum page: %2$d</string>
<string name="main_move">Move to page %1$d</string>
<string name="main_dialog_delete">Delete this gallery</string> <string name="main_dialog_delete">Delete this gallery</string>
<string name="help_dialog_title">WIP</string> <string name="help_dialog_title">WIP</string>
@@ -47,6 +56,7 @@
<string name="update_release_note"># Release Note(v%1$s)\n%2$s</string> <string name="update_release_note"># Release Note(v%1$s)\n%2$s</string>
<string name="search_hint">Search galleries</string> <string name="search_hint">Search galleries</string>
<string name="search_hint_with_page">Search galleries</string>
<string name="galleryblock_series">Series: %1$s</string> <string name="galleryblock_series">Series: %1$s</string>
<string name="galleryblock_type">Type: %1$s</string> <string name="galleryblock_type">Type: %1$s</string>

View File

@@ -20,6 +20,7 @@ allprojects {
repositories { repositories {
google() google()
jcenter() jcenter()
maven { url "https://jitpack.io" }
maven { maven {
url "s3://finotescore-android/release" url "s3://finotescore-android/release"
credentials(AwsCredentials) { credentials(AwsCredentials) {

View File

@@ -10,7 +10,7 @@ import java.util.*
import javax.net.ssl.HttpsURLConnection import javax.net.ssl.HttpsURLConnection
//galleryblock.js //galleryblock.js
fun fetchNozomi(area: String? = null, tag: String = "index", language: String = "all", start: Int = -1, count: Int = -1) : List<Int> { fun fetchNozomi(area: String? = null, tag: String = "index", language: String = "all", start: Int = -1, count: Int = -1) : Pair<List<Int>, Int> {
val url = val url =
when(area) { when(area) {
null -> "$protocol//$domain/$tag-$language$nozomiextension" null -> "$protocol//$domain/$tag-$language$nozomiextension"
@@ -28,6 +28,11 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
setRequestProperty("Range", "bytes=$startByte-$endByte") setRequestProperty("Range", "bytes=$startByte-$endByte")
} }
connect()
val totalItems = getHeaderField("Content-Range")
.replace(Regex("^[Bb]ytes \\d+-\\d+/"), "").toInt() / 4
val nozomi = ArrayList<Int>() val nozomi = ArrayList<Int>()
val arrayBuffer = ByteBuffer val arrayBuffer = ByteBuffer
@@ -37,10 +42,10 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
while (arrayBuffer.hasRemaining()) while (arrayBuffer.hasRemaining())
nozomi.add(arrayBuffer.int) nozomi.add(arrayBuffer.int)
return nozomi return Pair(nozomi, totalItems)
} }
} catch (e: Exception) { } catch (e: Exception) {
return emptyList() return Pair(emptyList(), 0)
} }
} }