Initial commit
This commit is contained in:
127
app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt
Normal file
127
app/src/main/java/xyz/quaver/pupil/GalleryActivity.kt
Normal file
@@ -0,0 +1,127 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import kotlinx.android.synthetic.main.activity_gallery.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.Reader
|
||||
import xyz.quaver.hitomi.getReader
|
||||
import xyz.quaver.hitomi.getReferer
|
||||
import xyz.quaver.pupil.adapters.GalleryAdapter
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class GalleryActivity : AppCompatActivity() {
|
||||
|
||||
private val images = ArrayList<String>()
|
||||
private var galleryID = 0
|
||||
private lateinit var reader: Deferred<Reader>
|
||||
private var loadJob: Job? = null
|
||||
private var screenMode = 0
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_gallery)
|
||||
|
||||
galleryID = intent.getIntExtra("GALLERY_ID", 0)
|
||||
CoroutineScope(Dispatchers.Unconfined).launch {
|
||||
reader = async(Dispatchers.IO) {
|
||||
getReader(galleryID)
|
||||
}
|
||||
}
|
||||
|
||||
initView()
|
||||
loadImages()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
loadJob?.cancel()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
gallery_recyclerview.adapter = GalleryAdapter(images).apply {
|
||||
setOnClick {
|
||||
val attrs = window.attributes
|
||||
|
||||
screenMode = (screenMode+1)%2
|
||||
|
||||
when(screenMode) {
|
||||
0 -> {
|
||||
attrs.flags = attrs.flags and WindowManager.LayoutParams.FLAG_FULLSCREEN.inv()
|
||||
supportActionBar?.show()
|
||||
}
|
||||
1 -> {
|
||||
attrs.flags = attrs.flags or WindowManager.LayoutParams.FLAG_FULLSCREEN
|
||||
supportActionBar?.hide()
|
||||
}
|
||||
}
|
||||
window.attributes = attrs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadImages() {
|
||||
|
||||
fun webpUrlFromUrl(url: URL) = URL(url.toString().replace("/galleries/", "/webp/") + ".webp")
|
||||
|
||||
loadJob = CoroutineScope(Dispatchers.Default).launch {
|
||||
val reader = reader.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
supportActionBar?.title = reader.title
|
||||
|
||||
with(gallery_progressbar) {
|
||||
max = reader.images.size
|
||||
progress = 0
|
||||
|
||||
visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
reader.images.chunked(8).forEach { chunked ->
|
||||
chunked.map {
|
||||
async(Dispatchers.IO) {
|
||||
val url = if (it.second?.haswebp == 1) webpUrlFromUrl(it.first) else it.first
|
||||
|
||||
val fileName: String
|
||||
|
||||
with(url.path) {
|
||||
fileName = substring(lastIndexOf('/')+1)
|
||||
}
|
||||
|
||||
val cache = File(cacheDir, "/imageCache/$galleryID/$fileName")
|
||||
|
||||
if (!cache.exists())
|
||||
with(url.openConnection() as HttpsURLConnection) {
|
||||
setRequestProperty("Referer", getReferer(galleryID))
|
||||
|
||||
if (!cache.parentFile.exists())
|
||||
cache.parentFile.mkdirs()
|
||||
|
||||
inputStream.copyTo(FileOutputStream(cache))
|
||||
}
|
||||
|
||||
cache.absolutePath
|
||||
}
|
||||
}.forEach {
|
||||
val cache = it.await()
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
images.add(cache)
|
||||
gallery_recyclerview.adapter?.notifyItemInserted(images.size - 1)
|
||||
gallery_progressbar.progress++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
gallery_progressbar.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
296
app/src/main/java/xyz/quaver/pupil/MainActivity.kt
Normal file
296
app/src/main/java/xyz/quaver/pupil/MainActivity.kt
Normal file
@@ -0,0 +1,296 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.preference.PreferenceManager
|
||||
import android.text.*
|
||||
import android.text.style.AlignmentSpan
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
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
|
||||
import com.google.android.material.appbar.AppBarLayout
|
||||
import kotlinx.android.synthetic.main.activity_main.*
|
||||
import kotlinx.coroutines.*
|
||||
import xyz.quaver.hitomi.*
|
||||
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
|
||||
import xyz.quaver.pupil.types.TagSuggestion
|
||||
import xyz.quaver.pupil.util.SetLineOverlap
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private val galleries = ArrayList<Pair<GalleryBlock, Bitmap?>>()
|
||||
|
||||
private var isLoading = false
|
||||
private var query = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
main_appbar_layout.addOnOffsetChangedListener(
|
||||
AppBarLayout.OnOffsetChangedListener { _, p1 ->
|
||||
main_searchview.translationY = p1.toFloat()
|
||||
main_recyclerview.translationY = p1.toFloat()
|
||||
}
|
||||
)
|
||||
|
||||
with(main_swipe_layout) {
|
||||
setProgressViewOffset(false, 0, resources.getDimensionPixelSize(R.dimen.progress_view_offset))
|
||||
|
||||
setOnRefreshListener {
|
||||
runBlocking {
|
||||
cleanJob?.join()
|
||||
}
|
||||
fetchGalleries(query, true)
|
||||
}
|
||||
}
|
||||
|
||||
setupRecyclerView()
|
||||
setupSearchBar()
|
||||
fetchGalleries(query)
|
||||
}
|
||||
|
||||
private fun setupRecyclerView() {
|
||||
with(main_recyclerview) {
|
||||
adapter = GalleryBlockAdapter(galleries).apply {
|
||||
setClickListener {
|
||||
val intent = Intent(this@MainActivity, GalleryActivity::class.java)
|
||||
intent.putExtra("GALLERY_ID", it)
|
||||
|
||||
//TODO: Maybe sprinke some transitions will be nice :D
|
||||
startActivity(intent)
|
||||
}
|
||||
}
|
||||
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 (!isLoading)
|
||||
if (layoutManager.findLastCompletelyVisibleItemPosition() == galleries.size)
|
||||
fetchGalleries(query)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var suggestionJob : Job? = null
|
||||
private fun setupSearchBar() {
|
||||
val searchInputView = findViewById<SearchInputView>(R.id.search_bar_text)
|
||||
//Change upper case letters to lower case
|
||||
searchInputView.addTextChangedListener(object: TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||
|
||||
}
|
||||
|
||||
override fun afterTextChanged(s: Editable?) {
|
||||
s ?: return
|
||||
|
||||
if (s.any { it.isUpperCase() })
|
||||
s.replace(0, s.length, s.toString().toLowerCase())
|
||||
}
|
||||
})
|
||||
|
||||
with(main_searchview as FloatingSearchView) {
|
||||
setOnMenuItemClickListener {
|
||||
when(it.itemId) {
|
||||
R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java))
|
||||
R.id.main_menu_search -> setSearchFocused(true)
|
||||
}
|
||||
}
|
||||
|
||||
setOnQueryChangeListener { _, query ->
|
||||
clearSuggestions()
|
||||
|
||||
if (query.isEmpty() or query.endsWith(' '))
|
||||
return@setOnQueryChangeListener
|
||||
|
||||
val currentQuery = query.split(" ").last().replace('_', ' ')
|
||||
|
||||
suggestionJob?.cancel()
|
||||
|
||||
suggestionJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
val suggestions = getSuggestionsForQuery(currentQuery).map { TagSuggestion(it) }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
swapSuggestions(suggestions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setOnBindSuggestionCallback { _, leftIcon, textView, item, _ ->
|
||||
val suggestion = item as TagSuggestion
|
||||
|
||||
leftIcon.setImageDrawable(
|
||||
ResourcesCompat.getDrawable(
|
||||
resources,
|
||||
when(suggestion.n) {
|
||||
"female" -> R.drawable.ic_gender_female
|
||||
"male" -> R.drawable.ic_gender_male
|
||||
"language" -> R.drawable.ic_translate
|
||||
"group" -> R.drawable.ic_account_group
|
||||
"character" -> R.drawable.ic_account_star
|
||||
"series" -> R.drawable.ic_book_open
|
||||
"artist" -> R.drawable.ic_brush
|
||||
else -> R.drawable.ic_tag
|
||||
},
|
||||
null)
|
||||
)
|
||||
|
||||
val text = "${suggestion.s}\n ${suggestion.t}"
|
||||
|
||||
val len = text.length
|
||||
val left = suggestion.s.length
|
||||
|
||||
textView.text = SpannableString(text).apply {
|
||||
val s = AlignmentSpan.Standard(Layout.Alignment.ALIGN_OPPOSITE)
|
||||
setSpan(s, left, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
setSpan(SetLineOverlap(true), 1, len-2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
setSpan(SetLineOverlap(false), len-1, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
|
||||
setOnSearchListener(object : FloatingSearchView.OnSearchListener {
|
||||
override fun onSuggestionClicked(searchSuggestion: SearchSuggestion?) {
|
||||
val suggestion = searchSuggestion as TagSuggestion
|
||||
|
||||
with(searchInputView.text) {
|
||||
delete(if (lastIndexOf(' ') == -1) 0 else lastIndexOf(' ')+1, length)
|
||||
append("${suggestion.n}:${suggestion.s.replace(Regex("\\s"), "_")} ")
|
||||
}
|
||||
|
||||
clearSuggestions()
|
||||
}
|
||||
|
||||
override fun onSearchAction(currentQuery: String?) {
|
||||
//Do search on onFocusCleared()
|
||||
}
|
||||
})
|
||||
|
||||
setOnFocusChangeListener(object: FloatingSearchView.OnFocusChangeListener {
|
||||
override fun onFocus() {
|
||||
//Do Nothing
|
||||
}
|
||||
|
||||
override fun onFocusCleared() {
|
||||
suggestionJob?.cancel()
|
||||
|
||||
val query = searchInputView.text.toString()
|
||||
|
||||
if (query != this@MainActivity.query) {
|
||||
this@MainActivity.query = query
|
||||
|
||||
fetchGalleries(query, true)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private val cache = ArrayList<Int>()
|
||||
private var currentFetchingJob: Job? = null
|
||||
private var cleanJob: Job? = null
|
||||
|
||||
private fun cancelFetch() {
|
||||
isLoading = false
|
||||
|
||||
runBlocking {
|
||||
cleanJob?.join()
|
||||
currentFetchingJob?.cancelAndJoin()
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchGalleries(query: String, clear: Boolean = false) {
|
||||
val preference = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val perPage = preference.getString("per_page", "25")?.toInt() ?: 25
|
||||
val defaultQuery = preference.getString("default_query", "")!!
|
||||
|
||||
if (clear) {
|
||||
cancelFetch()
|
||||
cleanJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
cache.clear()
|
||||
galleries.clear()
|
||||
|
||||
main_recyclerview.adapter?.notifyDataSetChanged()
|
||||
|
||||
main_noresult.visibility = View.INVISIBLE
|
||||
main_progressbar.show()
|
||||
main_swipe_layout.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
if (isLoading)
|
||||
return
|
||||
|
||||
isLoading = true
|
||||
|
||||
currentFetchingJob = CoroutineScope(Dispatchers.IO).launch {
|
||||
try {
|
||||
val galleryIDs: List<Int>
|
||||
|
||||
cleanJob?.join()
|
||||
|
||||
if (query.isEmpty() && defaultQuery.isEmpty())
|
||||
galleryIDs = fetchNozomi(start = galleries.size, count = perPage)
|
||||
else {
|
||||
if (cache.isEmpty())
|
||||
cache.addAll(doSearch("$defaultQuery $query"))
|
||||
|
||||
galleryIDs = cache.slice(galleries.size until Math.min(galleries.size + perPage, cache.size))
|
||||
|
||||
with(main_recyclerview.adapter as GalleryBlockAdapter) {
|
||||
noMore = galleries.size + perPage >= cache.size
|
||||
}
|
||||
}
|
||||
|
||||
if (query.isNotEmpty() and defaultQuery.isNotEmpty() and cache.isNullOrEmpty()) {
|
||||
withContext(Dispatchers.Main) {
|
||||
main_noresult.visibility = View.VISIBLE
|
||||
main_progressbar.hide()
|
||||
}
|
||||
}
|
||||
|
||||
galleryIDs.chunked(4).forEach { chunked ->
|
||||
chunked.map {
|
||||
async {
|
||||
val galleryBlock = getGalleryBlock(it)
|
||||
val thumbnail: Bitmap
|
||||
|
||||
with(galleryBlock.thumbnails[0].openConnection() as HttpsURLConnection) {
|
||||
thumbnail = BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
|
||||
Pair(galleryBlock, thumbnail)
|
||||
}
|
||||
}.forEach {
|
||||
val galleryBlock = it.await()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
main_progressbar.hide()
|
||||
|
||||
galleries.add(galleryBlock)
|
||||
main_recyclerview.adapter?.notifyItemInserted(galleries.size - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
80
app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
Normal file
80
app/src/main/java/xyz/quaver/pupil/SettingsActivity.kt
Normal file
@@ -0,0 +1,80 @@
|
||||
package xyz.quaver.pupil
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
|
||||
class SettingsActivity : AppCompatActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.settings_activity)
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, SettingsFragment())
|
||||
.commit()
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
||||
private val suffix = listOf(
|
||||
"B",
|
||||
"kB",
|
||||
"MB",
|
||||
"GB",
|
||||
"TB" //really?
|
||||
)
|
||||
|
||||
private fun getCacheSize() : String {
|
||||
var size = context!!.cacheDir.walk().map { it.length() }.sum()
|
||||
var suffixIndex = 0
|
||||
|
||||
while (size >= 1024) {
|
||||
size /= 1024
|
||||
suffixIndex++
|
||||
}
|
||||
|
||||
return getString(R.string.settings_delete_cache_summary, size, suffix[suffixIndex])
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
|
||||
with(findPreference<Preference>("delete_cache")) {
|
||||
this ?: return@with
|
||||
|
||||
summary = getCacheSize()
|
||||
|
||||
setOnPreferenceClickListener {
|
||||
AlertDialog.Builder(context).apply {
|
||||
setTitle(getString(R.string.settings_delete_cache_alert_title))
|
||||
setMessage(getString(R.string.settings_delete_cache_alert_message))
|
||||
setPositiveButton(android.R.string.yes) { _, _ ->
|
||||
with(context.cacheDir) {
|
||||
if (exists())
|
||||
deleteRecursively()
|
||||
}
|
||||
|
||||
summary = getCacheSize()
|
||||
}
|
||||
setNegativeButton(android.R.string.no) { _, _ -> }
|
||||
}.show()
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
|
||||
when (item?.itemId) {
|
||||
android.R.id.home -> onBackPressed()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
class GalleryAdapter(private val images: List<String>) : RecyclerView.Adapter<GalleryAdapter.ViewHolder>() {
|
||||
|
||||
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
|
||||
|
||||
private var onClick: (() -> Unit)? = null
|
||||
fun setOnClick(callback: (() -> Unit)?) {
|
||||
this.onClick = callback
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
LayoutInflater.from(parent.context).inflate(
|
||||
R.layout.item_gallery, parent, false
|
||||
).let {
|
||||
return ViewHolder(it as ImageView)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
with(holder.view) {
|
||||
setOnClickListener {
|
||||
onClick?.invoke()
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val options = BitmapFactory.Options()
|
||||
|
||||
options.inJustDecodeBounds = true
|
||||
BitmapFactory.decodeFile(images[position], options)
|
||||
|
||||
options.inSampleSize = options.outWidth /
|
||||
context.resources.displayMetrics.widthPixels
|
||||
|
||||
options.inJustDecodeBounds = false
|
||||
|
||||
val image = BitmapFactory.decodeFile(images[position], options)
|
||||
|
||||
launch(Dispatchers.Main) {
|
||||
setImageBitmap(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = images.size
|
||||
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package xyz.quaver.pupil.adapters
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.cardview.widget.CardView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.chip.Chip
|
||||
import kotlinx.android.synthetic.main.item_galleryblock.view.*
|
||||
import xyz.quaver.hitomi.GalleryBlock
|
||||
import xyz.quaver.hitomi.toTag
|
||||
import xyz.quaver.pupil.R
|
||||
|
||||
class GalleryBlockAdapter(private val galleries: List<Pair<GalleryBlock, Bitmap?>>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
||||
private enum class ViewType {
|
||||
VIEW_ITEM,
|
||||
VIEW_PROG
|
||||
}
|
||||
|
||||
private fun String.wordCapitalize() : String {
|
||||
val result = ArrayList<String>()
|
||||
|
||||
for (word in this.split(" "))
|
||||
result.add(word.capitalize())
|
||||
|
||||
return result.joinToString(" ")
|
||||
}
|
||||
|
||||
var noMore = false
|
||||
|
||||
class ViewHolder(val view: CardView) : RecyclerView.ViewHolder(view)
|
||||
class ProgressViewHolder(view: LinearLayout) : RecyclerView.ViewHolder(view)
|
||||
|
||||
private var callback: ((Int) -> Unit)? = null
|
||||
fun setClickListener(callback: ((Int) -> Unit)?) {
|
||||
this.callback = callback
|
||||
}
|
||||
|
||||
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 languages = resources.getStringArray(R.array.languages).map {
|
||||
it.split("|").let { split ->
|
||||
Pair(split[0], split[1])
|
||||
}
|
||||
}.toMap()
|
||||
val (gallery, thumbnail) = galleries[position]
|
||||
|
||||
val artists = gallery.artists.ifEmpty { listOf("N/A") }
|
||||
val series = gallery.series.ifEmpty { listOf("N/A") }
|
||||
|
||||
setOnClickListener {
|
||||
callback?.invoke(gallery.id)
|
||||
}
|
||||
|
||||
galleryblock_thumbnail.setImageBitmap(thumbnail)
|
||||
galleryblock_title.text = gallery.title
|
||||
galleryblock_artist.text = artists.joinToString(", ") { it.wordCapitalize() }
|
||||
galleryblock_series.text =
|
||||
resources.getString(R.string.galleryblock_series, series.joinToString(", ") { it.wordCapitalize() })
|
||||
galleryblock_type.text = resources.getString(R.string.galleryblock_type, gallery.type).wordCapitalize()
|
||||
galleryblock_language.text =
|
||||
resources.getString(R.string.galleryblock_language, languages[gallery.language])
|
||||
|
||||
galleryblock_tag_group.removeAllViews()
|
||||
gallery.relatedTags.forEach {
|
||||
galleryblock_tag_group.addView(
|
||||
Chip(context).apply {
|
||||
text = it.toTag().wordCapitalize()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount() = if (galleries.isEmpty()) 0 else galleries.size+(if (noMore) 0 else 1)
|
||||
|
||||
override fun getItemViewType(position: Int): Int {
|
||||
return when {
|
||||
galleries.getOrNull(position) == null -> ViewType.VIEW_PROG.ordinal
|
||||
else -> ViewType.VIEW_ITEM.ordinal
|
||||
}
|
||||
}
|
||||
}
|
||||
14
app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt
Normal file
14
app/src/main/java/xyz/quaver/pupil/types/TagSuggestion.kt
Normal file
@@ -0,0 +1,14 @@
|
||||
package xyz.quaver.pupil.types
|
||||
|
||||
import com.arlib.floatingsearchview.suggestions.model.SearchSuggestion
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import xyz.quaver.hitomi.Suggestion
|
||||
|
||||
@Parcelize
|
||||
data class TagSuggestion constructor(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion {
|
||||
constructor(s: Suggestion) : this(s.s, s.t, s.u, s.n)
|
||||
|
||||
override fun getBody(): String {
|
||||
return s
|
||||
}
|
||||
}
|
||||
37
app/src/main/java/xyz/quaver/pupil/util/SetLineOverlap.kt
Normal file
37
app/src/main/java/xyz/quaver/pupil/util/SetLineOverlap.kt
Normal file
@@ -0,0 +1,37 @@
|
||||
package xyz.quaver.pupil.util
|
||||
|
||||
import android.graphics.Paint
|
||||
import android.text.style.LineHeightSpan
|
||||
|
||||
class SetLineOverlap(private val overlap: Boolean) : LineHeightSpan {
|
||||
companion object {
|
||||
private var originalBottom = 15
|
||||
private var originalDescent = 13
|
||||
private var overlapSaved = false
|
||||
}
|
||||
|
||||
override fun chooseHeight(
|
||||
text: CharSequence?,
|
||||
start: Int,
|
||||
end: Int,
|
||||
spanstartv: Int,
|
||||
lineHeight: Int,
|
||||
fm: Paint.FontMetricsInt?
|
||||
) {
|
||||
fm ?: return
|
||||
|
||||
if (overlap) {
|
||||
if (overlapSaved) {
|
||||
originalBottom = fm.bottom
|
||||
originalDescent = fm.descent
|
||||
overlapSaved = true
|
||||
}
|
||||
fm.bottom += fm.top
|
||||
fm.descent += fm.top
|
||||
} else {
|
||||
fm.bottom = originalBottom
|
||||
fm.descent = originalDescent
|
||||
overlapSaved = false
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user