Add paging
This commit is contained in:
tom5079
2019-06-01 21:42:14 +09:00
parent f326c69902
commit 530da98ec6
21 changed files with 605 additions and 204 deletions

2
.idea/misc.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<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" />
</component>
</project>

View File

@@ -9,8 +9,8 @@ android {
applicationId "xyz.quaver.pupil"
minSdkVersion 16
targetSdkVersion 28
versionCode 7
versionName "2.1"
versionCode 8
versionName "2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {

View File

@@ -6,16 +6,17 @@ import android.os.Bundle
import android.preference.PreferenceManager
import android.text.*
import android.text.style.AlignmentSpan
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.util.Log
import android.view.*
import android.widget.EditText
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.cardview.widget.CardView
import androidx.core.content.res.ResourcesCompat
import androidx.core.view.GravityCompat
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arlib.floatingsearchview.FloatingSearchView
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
import com.arlib.floatingsearchview.util.view.SearchInputView
@@ -39,6 +40,7 @@ import java.net.URL
import java.util.*
import javax.net.ssl.HttpsURLConnection
import kotlin.collections.ArrayList
import kotlin.math.roundToInt
class MainActivity : AppCompatActivity() {
@@ -56,7 +58,9 @@ class MainActivity : AppCompatActivity() {
private val SETTINGS = 45162
private var galleryIDs: Deferred<List<Int>>? = null
private var totalItems = 0
private var loadingJob: Job? = null
private var currentPage = 0
private lateinit var histories: Histories
private lateinit var downloads: Histories
@@ -64,16 +68,25 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val preference = PreferenceManager.getDefaultSharedPreferences(this)
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
}
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
setContentView(R.layout.activity_main)
checkUpdate()
@@ -109,6 +122,44 @@ class MainActivity : AppCompatActivity() {
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?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode) {
@@ -188,31 +239,16 @@ class MainActivity : AppCompatActivity() {
}
private fun initView() {
var prevP1 = 0
main_appbar_layout.addOnOffsetChangedListener(
AppBarLayout.OnOffsetChangedListener { _, p1 ->
main_searchview.translationY = p1.toFloat()
main_recyclerview.translationY = p1.toFloat()
main_recyclerview.scrollBy(0, prevP1 - p1)
prevP1 = p1
}
)
//SwipeRefreshLayout
with(main_swipe_layout) {
setProgressViewOffset(
false,
resources.getDimensionPixelSize(R.dimen.progress_view_start),
resources.getDimensionPixelSize(R.dimen.progress_view_offset)
)
setOnRefreshListener {
post {
cancelFetch()
clearGalleries()
fetchGalleries(query)
loadBlocks()
}
}
}
//NavigationView
main_nav_view.setNavigationItemSelectedListener {
runOnUiThread {
@@ -276,7 +312,7 @@ class MainActivity : AppCompatActivity() {
with(main_recyclerview) {
adapter = GalleryBlockAdapter(galleries).apply {
onChipClickedHandler.add {
post {
runOnUiThread {
query = it.toQuery()
this@MainActivity.findViewById<SearchInputView>(R.id.search_bar_text)
.setText(query, TextView.BufferType.EDITABLE)
@@ -288,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)
.setOnItemClickListener { _, position, _ ->
.setOnItemClickListener { _, position, v ->
if (v !is CardView)
return@setOnItemClickListener
val intent = Intent(this@MainActivity, ReaderActivity::class.java)
val gallery = galleries[position].first
intent.putExtra("galleryblock", Json(JsonConfiguration.Stable).stringify(GalleryBlock.serializer(), gallery))
@@ -311,7 +338,11 @@ class MainActivity : AppCompatActivity() {
startActivity(intent)
histories.add(gallery.id)
}.setOnItemLongClickListener { recyclerView, position, _ ->
}.setOnItemLongClickListener { recyclerView, position, v ->
if (v !is CardView)
return@setOnItemLongClickListener true
val galleryBlock = galleries[position].first
val view = LayoutInflater.from(this@MainActivity)
.inflate(R.layout.dialog_galleryblock, recyclerView, false)
@@ -358,6 +389,210 @@ class MainActivity : AppCompatActivity() {
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) {
Log.d("Pupil", "hmm...")
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
}
}
}
@@ -386,7 +621,33 @@ class MainActivity : AppCompatActivity() {
setOnMenuItemClickListener {
when(it.itemId) {
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()
}
}
}
@@ -471,7 +732,7 @@ class MainActivity : AppCompatActivity() {
if (query != this@MainActivity.query) {
this@MainActivity.query = query
CoroutineScope(Dispatchers.Main).launch {
runOnUiThread {
cancelFetch()
clearGalleries()
fetchGalleries(query)
@@ -502,12 +763,12 @@ class MainActivity : AppCompatActivity() {
this.notifyDataSetChanged()
}
main_appbar_layout.setExpanded(true)
main_noresult.visibility = View.INVISIBLE
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 perPage = preference.getString("per_page", "25")?.toInt() ?: 25
val defaultQuery = preference.getString("default_query", "")!!
@@ -521,27 +782,42 @@ class MainActivity : AppCompatActivity() {
when(mode) {
Mode.SEARCH -> {
when {
query.isEmpty() and defaultQuery.isEmpty() ->
fetchNozomi(start = from, count = perPage)
else ->
doSearch("$defaultQuery $query")
query.isEmpty() and defaultQuery.isEmpty() -> {
fetchNozomi(start = currentPage*perPage, count = perPage).let {
totalItems = it.second
it.first
}
}
else -> doSearch("$defaultQuery $query").apply {
totalItems = size
}
}
}
Mode.HISTORY -> {
when {
query.isEmpty() -> histories.toList()
query.isEmpty() -> {
histories.toList().apply {
totalItems = size
}
}
else -> {
val result = doSearch(query).sorted()
histories.filter { result.binarySearch(it) >= 0 }
histories.filter { result.binarySearch(it) >= 0 }.apply {
totalItems = size
}
}
}
}
Mode.DOWNLOAD -> {
when {
query.isEmpty() -> downloads.toList()
query.isEmpty() -> downloads.toList().apply {
totalItems = size
}
else -> {
val result = doSearch(query).sorted()
downloads.filter { result.binarySearch(it) >= 0 }
downloads.filter { result.binarySearch(it) >= 0 }.apply {
totalItems = size
}
}
}
}
@@ -566,18 +842,11 @@ class MainActivity : AppCompatActivity() {
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() ->
query.isEmpty() and defaultQuery.isEmpty() and (mode == Mode.SEARCH) ->
galleryIDs
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 ->
for (chunk in chunks)
chunk.map {
@@ -635,8 +904,7 @@ class MainActivity : AppCompatActivity() {
if (galleryBlock != null) {
galleries.add(galleryBlock)
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
main_recyclerview.adapter!!.notifyItemInserted(galleries.size - 1)
}
}
}

View File

@@ -12,12 +12,14 @@ import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.vectordrawable.graphics.drawable.Animatable2Compat
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.view.*
import kotlinx.android.synthetic.main.dialog_numberpicker.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.io.IOException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration
import xyz.quaver.hitomi.GalleryBlock
@@ -89,7 +91,7 @@ class ReaderActivity : AppCompatActivity() {
when(item?.itemId) {
R.id.reader_menu_page_indicator -> {
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
maxValue=gallerySize
value=currentPage
@@ -97,8 +99,8 @@ class ReaderActivity : AppCompatActivity() {
val dialog = AlertDialog.Builder(this).apply {
setView(view)
}.create()
view.reader_dialog_ok.setOnClickListener {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.reader_dialog_number_picker.value-1, 0)
view.dialog_ok.setOnClickListener {
(reader_recyclerview.layoutManager as LinearLayoutManager?)?.scrollToPositionWithOffset(view.dialog_number_picker.value-1, 0)
dialog.dismiss()
}
@@ -135,7 +137,13 @@ class ReaderActivity : AppCompatActivity() {
var d: GalleryDownloader? = GalleryDownloader.get(galleryBlock.id)
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 {
@@ -170,6 +178,8 @@ class ReaderActivity : AppCompatActivity() {
}
}
onErrorHandler = {
if (it is IOException)
Snackbar.make(reader_layout, R.string.unable_to_connect, Snackbar.LENGTH_LONG).show()
downloader.download = false
}
onCompleteHandler = {

View File

@@ -1,12 +1,13 @@
package xyz.quaver.pupil.adapters
import android.graphics.BitmapFactory
import android.util.SparseArray
import android.util.Log
import android.util.SparseBooleanArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
@@ -27,66 +28,28 @@ import xyz.quaver.pupil.types.Tag
import java.io.File
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.concurrent.schedule
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferred<String>>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private enum class ViewType {
VIEW_ITEM,
VIEW_PROG
enum class ViewType {
NEXT,
GALLERY,
PREV
}
private fun String.wordCapitalize() : String {
val result = ArrayList<String>()
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) {
inner class GalleryViewHolder(private val view: CardView) : RecyclerView.ViewHolder(view) {
fun bind(item: Pair<GalleryBlock, Deferred<String>>) {
with(view) {
val resources = context.resources
val languages = resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
val (gallery, thumbnail) = galleries[position]
holder.galleryID = gallery.id
val (gallery: GalleryBlock, thumbnail: Deferred<String>) = item
val artists = gallery.artists
val series = gallery.series
@@ -99,7 +62,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val bitmap = BitmapFactory.decodeFile(thumbnail.await())
CoroutineScope(Dispatchers.Main).launch {
post {
galleryblock_thumbnail.setImageBitmap(bitmap)
}
}
@@ -122,10 +85,10 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
galleryblock_progressbar.visibility = View.GONE
}
if (refreshTasks.get(gallery.id) == null) {
if (refreshTasks[this@GalleryViewHolder] == null) {
val refresh = Timer(false).schedule(0, 1000) {
post {
with(galleryblock_progressbar) {
with(view.galleryblock_progressbar) {
progress = imageCache.list()?.size ?: 0
if (!readerCache.exists()) {
@@ -133,7 +96,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
max = 0
progress = 0
holder.view.galleryblock_progress_complete.visibility = View.INVISIBLE
view.galleryblock_progress_complete.visibility = View.INVISIBLE
} else {
if (visibility == View.GONE) {
val reader = Json(JsonConfiguration.Stable)
@@ -144,24 +107,21 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
if (progress == max) {
if (completeFlag.get(gallery.id, false)) {
with(holder.view.galleryblock_progress_complete) {
with(view.galleryblock_progress_complete) {
setImageResource(R.drawable.ic_progressbar)
visibility = View.VISIBLE
}
} else {
val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_progressbar_complete)
with(holder.view.galleryblock_progress_complete) {
with(view.galleryblock_progress_complete) {
setImageDrawable(drawable)
visibility = View.VISIBLE
}
drawable?.start()
completeFlag.put(gallery.id, true)
}
} else {
with(holder.view.galleryblock_progress_complete) {
visibility = View.INVISIBLE
}
}
} else
view.galleryblock_progress_complete.visibility = View.INVISIBLE
null
}
@@ -169,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
@@ -205,7 +165,7 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
val tag = Tag.parse(it)
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) {
"male" -> {
@@ -232,32 +192,85 @@ class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Deferre
}
}
}
if (holder is ProgressViewHolder) {
holder.view.visibility = when(noMore) {
true -> View.GONE
false -> View.VISIBLE
}
class NextViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
class PrevViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
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) {
super.onViewDetachedFromWindow(holder)
if (holder is ViewHolder) {
val galleryID = holder.galleryID ?: return
val task = refreshTasks.get(galleryID) ?: return
if (holder is GalleryViewHolder) {
val task = refreshTasks[holder] ?: return
refreshTasks.remove(galleryID)
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 {
return when {
galleries.getOrNull(position) == null -> ViewType.VIEW_PROG.ordinal
else -> ViewType.VIEW_ITEM.ordinal
}
showPrev && position == 0 -> ViewType.PREV
showNext && position == galleries.size+(if (showPrev) 1 else 0) -> ViewType.NEXT
else -> ViewType.GALLERY
}.ordinal
}
}

View File

@@ -55,7 +55,7 @@ class GalleryDownloader(
var onReaderLoadedHandler: ((Reader) -> Unit)? = null
var onProgressHandler: ((Int) -> Unit)? = null
var onDownloadedHandler: ((List<String>) -> Unit)? = null
var onErrorHandler: (() -> Unit)? = null
var onErrorHandler: ((Exception) -> Unit)? = null
var onCompleteHandler: (() -> Unit)? = null
var onNotifyChangedHandler: ((Boolean) -> Unit)? = null
@@ -100,15 +100,13 @@ class GalleryDownloader(
}
}
//Could not retrieve reader
if (reader.isEmpty())
throw IOException("Can't retrieve Reader")
if (reader.isNotEmpty()) {
//Save cache
if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
//Save cache
if (!cache.parentFile.exists())
cache.parentFile.mkdirs()
cache.writeText(json.stringify(serializer, reader))
cache.writeText(json.stringify(serializer, reader))
}
reader
}
@@ -120,6 +118,9 @@ class GalleryDownloader(
downloadJob = CoroutineScope(Dispatchers.Default).launch {
val reader = reader.await()
if (reader.isEmpty())
onErrorHandler?.invoke(IOException("Couldn't retrieve Reader"))
val list = ArrayList<String>()
onReaderLoadedHandler?.invoke(reader)
@@ -162,7 +163,7 @@ class GalleryDownloader(
} catch (e: Exception) {
cache.delete()
onErrorHandler?.invoke()
onErrorHandler?.invoke(e)
notificationBuilder
.setContentTitle(galleryBlock.title)

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

View File

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

View File

@@ -7,7 +7,7 @@
<TextView
style="?android:textAppearanceLarge"
android:id="@+id/reader_dialog_title"
android:id="@+id/dialog_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/reader_go_to_page"
@@ -15,20 +15,20 @@
app:layout_constraintStart_toStartOf="parent"/>
<NumberPicker
android:id="@+id/reader_dialog_number_picker"
android:id="@+id/dialog_number_picker"
android:layout_width="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_constraintEnd_toEndOf="parent"/>
<Button
android:id="@+id/reader_dialog_ok"
android:id="@+id/dialog_ok"
style="?borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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_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

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

View File

@@ -3,7 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<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"/>
</menu>

View File

@@ -6,12 +6,12 @@
<string name="main_no_result">結果なし</string>
<string name="main_search">検索</string>
<string name="search_hint">ギャラリー検索</string>
<string name="search_hint_with_page">ギャラリー検索</string>
<string name="settings_cache_title">キャッシュ</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>
<string name="permission_explain">権限を拒否すると一部の機能が利用できません</string>
<string name="settings_galleries_per_page">一回にロードするギャラリー数</string>
<string name="settings_search_title">検索設定</string>
<string name="settings_title">設定</string>
@@ -53,4 +53,10 @@
<string name="reader_fab_download_cancel">バックグラウンドダウンロード中止</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>

View File

@@ -3,8 +3,8 @@
<string name="galleryblock_language">언어: %1$s</string>
<string name="galleryblock_series">시리즈: %1$s</string>
<string name="galleryblock_type">종류: %1$s</string>
<string name="permission_explain">권한을 거부하면 일부 기능이 작동하지 않을 수 있습니다</string>
<string name="search_hint">갤러리 검색</string>
<string name="search_hint_with_page">갤러리 검색</string>
<string name="settings_default_query">기본 검색어</string>
<string name="settings_clear_image_cache">이미지 캐시 정리하기</string>
<string name="settings_clear_cache_alert_message">캐시를 정리하면 이미지 로딩속도가 느려질 수 있습니다. 계속하시겠습니까?</string>
@@ -53,4 +53,10 @@
<string name="reader_fab_download_cancel">백그라운드 다운로드 취소</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>

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="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>
@@ -20,11 +20,14 @@
<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_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_no_result">No result</string>
@@ -37,6 +40,11 @@
<string name="main_drawer_group_contact_github">Visit github</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="help_dialog_title">WIP</string>
@@ -48,6 +56,7 @@
<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_with_page">Search galleries</string>
<string name="galleryblock_series">Series: %1$s</string>
<string name="galleryblock_type">Type: %1$s</string>

View File

@@ -10,7 +10,7 @@ import java.util.*
import javax.net.ssl.HttpsURLConnection
//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 =
when(area) {
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")
}
connect()
val totalItems = getHeaderField("Content-Range")
.replace(Regex("^[Bb]ytes \\d+-\\d+/"), "").toInt() / 4
val nozomi = ArrayList<Int>()
val arrayBuffer = ByteBuffer
@@ -37,10 +42,10 @@ fun fetchNozomi(area: String? = null, tag: String = "index", language: String =
while (arrayBuffer.hasRemaining())
nozomi.add(arrayBuffer.int)
return nozomi
return Pair(nozomi, totalItems)
}
} catch (e: Exception) {
return emptyList()
return Pair(emptyList(), 0)
}
}