Added gallery details

Added dark mode
This commit is contained in:
tom5079
2019-07-21 20:51:50 +09:00
parent edacef0f2b
commit 2afdc5591a
26 changed files with 573 additions and 155 deletions

View File

@@ -14,7 +14,7 @@ android {
minSdkVersion 16 minSdkVersion 16
targetSdkVersion 29 targetSdkVersion 29
versionCode 26 versionCode 26
versionName "3.2" versionName "3.2-beta"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
@@ -25,7 +25,7 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
buildTypes.each { buildTypes.each {
it.buildConfigField('boolean', 'PRERELEASE', 'false') it.buildConfigField('boolean', 'PRERELEASE', 'true')
} }
} }
kotlinOptions { kotlinOptions {
@@ -50,12 +50,13 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.preference:preference:1.1.0-rc01' implementation 'androidx.preference:preference:1.1.0-rc01'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3' implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'androidx.gridlayout:gridlayout:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.0.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.1.0-alpha08'
implementation 'com.google.firebase:firebase-core:17.0.0' implementation 'com.google.firebase:firebase-core:17.0.1'
implementation 'com.google.firebase:firebase-perf:18.0.1' implementation 'com.google.firebase:firebase-perf:18.0.1'
implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1'
implementation 'com.github.arimorty:floatingsearchview:2.1.1' implementation 'com.github.arimorty:floatingsearchview:2.1.1'

View File

@@ -18,13 +18,10 @@
package xyz.quaver.pupil package xyz.quaver.pupil
import android.app.DownloadManager
import android.app.Notification import android.app.Notification
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
@@ -76,6 +73,11 @@ class Pupil : MultiDexApplication() {
preference.edit().putBoolean("channel_created", true).apply() preference.edit().putBoolean("channel_created", true).apply()
} }
AppCompatDelegate.setDefaultNightMode(when (preference.getBoolean("dark_mode", false)) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
super.onCreate() super.onCreate()
} }

View File

@@ -18,20 +18,17 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.app.AlertDialog
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
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.ArrayAdapter
import android.widget.LinearLayout import android.widget.LinearLayout
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
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.bumptech.glide.Glide
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
@@ -49,6 +46,7 @@ import xyz.quaver.pupil.R
import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.Histories import xyz.quaver.pupil.util.Histories
import xyz.quaver.pupil.util.getCachedGallery import xyz.quaver.pupil.util.getCachedGallery
import xyz.quaver.pupil.util.wordCapitalize
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -168,19 +166,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
artists.isNotEmpty() -> View.VISIBLE artists.isNotEmpty() -> View.VISIBLE
else -> View.GONE else -> View.GONE
} }
setOnClickListener {
if (artists.size > 1) {
AlertDialog.Builder(context).apply {
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, artists)) { _, index ->
for (callback in onChipClickedHandler)
callback.invoke(Tag("artist", artists[index]))
}
}.show()
} else {
for(callback in onChipClickedHandler)
callback.invoke(Tag("artist", artists.first()))
}
}
} }
with(galleryblock_series) { with(galleryblock_series) {
text = text =
@@ -191,31 +176,8 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
series.isNotEmpty() -> View.VISIBLE series.isNotEmpty() -> View.VISIBLE
else -> View.GONE else -> View.GONE
} }
setOnClickListener {
setOnClickListener {
if (series.size > 1) {
AlertDialog.Builder(context).apply {
setAdapter(ArrayAdapter(context, android.R.layout.select_dialog_item, series)) { _, index ->
for (callback in onChipClickedHandler)
callback.invoke(Tag("series", series[index]))
}
}.show()
} else {
for(callback in onChipClickedHandler)
callback.invoke(Tag("series", series.first()))
}
}
}
}
with(galleryblock_type) {
text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
setOnClickListener {
setOnClickListener {
for(callback in onChipClickedHandler)
callback.invoke(Tag("type", galleryBlock.type))
}
}
} }
galleryblock_type.text = resources.getString(R.string.galleryblock_type, galleryBlock.type).wordCapitalize()
with(galleryblock_language) { with(galleryblock_language) {
text = text =
resources.getString(R.string.galleryblock_language, languages[galleryBlock.language]) resources.getString(R.string.galleryblock_language, languages[galleryBlock.language])
@@ -223,48 +185,37 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
galleryBlock.language.isNotEmpty() -> View.VISIBLE galleryBlock.language.isNotEmpty() -> View.VISIBLE
else -> View.GONE else -> View.GONE
} }
setOnClickListener {
setOnClickListener {
for(callback in onChipClickedHandler)
callback.invoke(Tag("language", galleryBlock.language))
}
}
} }
galleryblock_tag_group.removeAllViews() galleryblock_tag_group.removeAllViews()
galleryBlock.relatedTags.forEach { galleryBlock.relatedTags.forEach {
val tag = Tag.parse(it).let { tag -> galleryblock_tag_group.addView(Chip(context).apply {
when { val tag = Tag.parse(it).let { tag ->
tag.area != null -> tag when {
else -> Tag("tag", it) tag.area != null -> tag
else -> Tag("tag", it)
}
} }
}
val chip = LayoutInflater.from(context) chipIcon = when(tag.area) {
.inflate(R.layout.tag_chip, this, false) as Chip "male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
val icon = when(tag.area) { setTextColor(ContextCompat.getColor(context, android.R.color.white))
"male" -> { ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
chip.setChipBackgroundColorResource(R.color.material_blue_700) }
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white)) "female" -> {
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white) setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
} }
"female" -> { text = tag.tag.wordCapitalize()
chip.setChipBackgroundColorResource(R.color.material_pink_600) setOnClickListener {
chip.setTextColor(ContextCompat.getColor(context, android.R.color.white)) for (callback in onChipClickedHandler)
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white) callback.invoke(tag)
} }
else -> null })
}
chip.chipIcon = icon
chip.text = tag.tag.wordCapitalize()
chip.setOnClickListener {
for (callback in onChipClickedHandler)
callback.invoke(tag)
}
galleryblock_tag_group.addView(chip)
} }
galleryblock_id.text = galleryBlock.id.toString() galleryblock_id.text = galleryBlock.id.toString()
@@ -316,15 +267,6 @@ class GalleryBlockAdapter(private val glide: RequestManager, private val galleri
} }
} }
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>() private val refreshTasks = HashMap<GalleryViewHolder, TimerTask>()
val completeFlag = SparseBooleanArray() val completeFlag = SparseBooleanArray()

View File

@@ -18,7 +18,6 @@
package xyz.quaver.pupil.adapters package xyz.quaver.pupil.adapters
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -26,7 +25,6 @@ import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager import com.bumptech.glide.RequestManager
import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.target.Target
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@@ -0,0 +1,42 @@
/*
* Pupil, Hitomi.la viewer for Android
* Copyright (C) 2019 tom5079
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package xyz.quaver.pupil.adapters
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.RequestManager
class ThumbnailAdapter(private val glide: RequestManager, private val thumbnails: List<String>) : RecyclerView.Adapter<ThumbnailAdapter.ViewHolder>() {
class ViewHolder(val view: ImageView) : RecyclerView.ViewHolder(view)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ImageView(parent.context))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
glide
.load(thumbnails[position])
.into(holder.view)
}
override fun getItemCount() = thumbnails.size
}

View File

@@ -20,21 +20,51 @@ package xyz.quaver.pupil.ui
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.LinearLayout.LayoutParams
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.gridlayout.widget.GridLayout
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.Target import com.bumptech.glide.RequestManager
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.dialog_galleryblock.* import kotlinx.android.synthetic.main.dialog_galleryblock.*
import kotlinx.coroutines.CoroutineScope import kotlinx.android.synthetic.main.gallery_details.view.*
import kotlinx.coroutines.Dispatchers import kotlinx.android.synthetic.main.item_gallery_details.view.*
import kotlinx.coroutines.launch import kotlinx.coroutines.*
import xyz.quaver.hitomi.Gallery
import xyz.quaver.hitomi.GalleryBlock
import xyz.quaver.hitomi.getGallery import xyz.quaver.hitomi.getGallery
import xyz.quaver.hitomi.getGalleryBlock
import xyz.quaver.pupil.Pupil
import xyz.quaver.pupil.R import xyz.quaver.pupil.R
import xyz.quaver.pupil.adapters.GalleryBlockAdapter
import xyz.quaver.pupil.adapters.ThumbnailAdapter
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.ItemClickSupport
import xyz.quaver.pupil.util.wordCapitalize
class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) { class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(context) {
private val languages = context.resources.getStringArray(R.array.languages).map {
it.split("|").let { split ->
Pair(split[0], split[1])
}
}.toMap()
private val glide = Glide.with(context)
val onChipClickedHandler = ArrayList<((Tag) -> (Unit))>()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.dialog_galleryblock) setContentView(R.layout.dialog_galleryblock)
@@ -42,22 +72,53 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
window?.attributes.apply { window?.attributes.apply {
this ?: return@apply this ?: return@apply
width = LinearLayout.LayoutParams.MATCH_PARENT width = LayoutParams.MATCH_PARENT
height = LinearLayout.LayoutParams.MATCH_PARENT height = LayoutParams.MATCH_PARENT
} }
gallery_fab.setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right)) with(gallery_fab) {
setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right))
setOnClickListener {
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleryID)
})
(context.applicationContext as Pupil).histories.add(galleryID)
}
}
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
val gallery = getGallery(galleryID) val gallery = getGallery(galleryID)
launch(Dispatchers.Main) { launch(Dispatchers.Main) {
gallery_toolbar.title = gallery.title gallery_progressbar.visibility = View.GONE
gallery_title.text = gallery.title
gallery_artist.text = gallery.artists.joinToString(", ") { it.wordCapitalize() }
with(gallery_type) {
text = gallery.type.wordCapitalize()
setOnClickListener {
gallery.type.let {
when (it) {
"artist CG" -> "artistcg"
"game CG" -> "gamecg"
else -> it
}
}.let {
onChipClickedHandler.forEach { handler ->
handler.invoke(Tag("type", it))
}
}
}
}
Glide.with(context) Glide.with(context)
.load(gallery.thumbnails[0]) .load(gallery.thumbnails.firstOrNull())
.into(gallery_thumbnail) .into(gallery_thumbnail)
addDetails(gallery)
addThumbnails(gallery)
addRelated(gallery)
} }
} catch (e: Exception) { } catch (e: Exception) {
Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show() Snackbar.make(gallery_layout, R.string.unable_to_connect, Snackbar.LENGTH_INDEFINITE).show()
@@ -65,4 +126,151 @@ class GalleryDialog(context: Context, private val galleryID: Int) : Dialog(conte
} }
} }
private fun addDetails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_details)
listOf(
R.string.gallery_artists,
R.string.gallery_groups,
R.string.gallery_language,
R.string.gallery_series,
R.string.gallery_characters,
R.string.gallery_tags
).zip(
listOf(
gallery.artists.map { Tag("artist", it) },
gallery.groups.map { Tag("group", it) },
listOf(gallery.language).map { Tag("language", it) },
gallery.series.map { Tag("series", it) },
gallery.characters.map { Tag("character", it) },
gallery.tags.map {
Tag.parse(it).let { tag ->
when {
tag.area != null -> tag
else -> Tag("tag", it)
}
}
}
)
).filter {
(_, content) -> content.isNotEmpty()
}.forEach { (title, content) ->
inflater.inflate(R.layout.item_gallery_details, gallery_details_contents, false).apply {
gallery_details_type.setText(title)
content.forEach { tag ->
gallery_details_tags.addView(
Chip(context).apply {
chipIcon = when(tag.area) {
"male" -> {
setChipBackgroundColorResource(R.color.material_blue_700)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_male_white)
}
"female" -> {
setChipBackgroundColorResource(R.color.material_pink_600)
setTextColor(ContextCompat.getColor(context, android.R.color.white))
ContextCompat.getDrawable(context, R.drawable.ic_gender_female_white)
}
else -> null
}
text = when (tag.area) {
"language" -> languages[tag.tag]
else -> tag.tag.wordCapitalize()
}
setOnClickListener {
onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
)
}
}.let {
gallery_details_contents.addView(it)
}
}
}.let {
gallery_contents.addView(it)
}
}
private fun addThumbnails(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_thumbnails)
RecyclerView(context).apply {
layoutManager = GridLayoutManager(context, 3)
adapter = ThumbnailAdapter(glide, gallery.thumbnails)
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
private fun addRelated(gallery: Gallery) {
val inflater = LayoutInflater.from(context)
val galleries = ArrayList<Pair<GalleryBlock, Deferred<String>>>()
val adapter = GalleryBlockAdapter(glide, galleries).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
}
}
CoroutineScope(Dispatchers.Main).launch {
gallery.related.forEachIndexed { i, galleryID ->
async(Dispatchers.IO) {
getGalleryBlock(galleryID)
}.let {
val galleryBlock = it.await() ?: return@let
galleries.add(Pair(galleryBlock, GlobalScope.async { galleryBlock.thumbnails.first() }))
adapter.notifyItemInserted(i)
}
}
}
inflater.inflate(R.layout.gallery_details, gallery_contents, false).apply {
gallery_details.setText(R.string.gallery_related)
RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, _ ->
context.startActivity(Intent(context, ReaderActivity::class.java).apply {
putExtra("galleryID", galleries[position].first.id)
})
(context.applicationContext as Pupil).histories.add(galleries[position].first.id)
}
.setOnItemLongClickListener { _, position, _ ->
GalleryDialog(context, galleries[position].first.id).apply {
onChipClickedHandler.add { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { it.invoke(tag) }
}
}.show()
true
}
}.let {
gallery_details_contents.addView(it, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
}
}.let {
gallery_contents.addView(it)
}
}
} }

View File

@@ -58,7 +58,7 @@ class LockActivity : AppCompatActivity() {
when(mode) { when(mode) {
null -> { null -> {
if (lockManager.empty()) { if (lockManager.isEmpty()) {
setResult(RESULT_OK) setResult(RESULT_OK)
finish() finish()
return return

View File

@@ -127,7 +127,21 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK) val lockManager = try {
LockManager(this)
} catch (e: Exception) {
android.app.AlertDialog.Builder(this).apply {
setTitle(R.string.warning)
setMessage(R.string.lock_corrupted)
setPositiveButton(android.R.string.ok) { _, _ ->
finish()
}
}.show()
return
}
if (lockManager.isNotEmpty())
startActivityForResult(Intent(this, LockActivity::class.java), REQUEST_LOCK)
checkPermissions() checkPermissions()
@@ -524,7 +538,6 @@ class MainActivity : AppCompatActivity() {
} }
ItemClickSupport.addTo(this) ItemClickSupport.addTo(this)
.setOnItemClickListener { _, position, v -> .setOnItemClickListener { _, position, v ->
if (v !is CardView) if (v !is CardView)
return@setOnItemClickListener return@setOnItemClickListener
@@ -543,8 +556,20 @@ class MainActivity : AppCompatActivity() {
val galleryID = galleries[position].first.id val galleryID = galleries[position].first.id
GalleryDialog(this@MainActivity, galleryID) GalleryDialog(this@MainActivity, galleryID).apply {
.show() onChipClickedHandler.add {
runOnUiThread {
query = it.toQuery()
currentPage = 0
cancelFetch()
clearGalleries()
fetchGalleries(query, sortMode)
loadBlocks()
}
dismiss()
}
}.show()
true true
} }

View File

@@ -31,6 +31,7 @@ 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.appcompat.app.AppCompatDelegate
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
@@ -338,6 +339,19 @@ class SettingsActivity : AppCompatActivity() {
true true
} }
} }
with(findPreference<Preference>("dark_mode")) {
this!!
onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _, newValue ->
AppCompatDelegate.setDefaultNightMode(when (newValue as Boolean) {
true -> AppCompatDelegate.MODE_NIGHT_YES
false -> AppCompatDelegate.MODE_NIGHT_NO
})
true
}
}
} }
} }

View File

@@ -112,10 +112,12 @@ class LockManager(base: Context): ContextWrapper(base) {
} }
} }
fun empty(): Boolean { fun isEmpty(): Boolean {
return locks.isNullOrEmpty() return locks.isNullOrEmpty()
} }
fun isNotEmpty(): Boolean = !isEmpty()
fun contains(type: Lock.Type): Boolean { fun contains(type: Lock.Type): Boolean {
return locks?.any { it.type == type } ?: false return locks?.any { it.type == type } ?: false
} }

View File

@@ -23,4 +23,13 @@ import android.content.pm.PackageManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
fun Context.hasPermission(permission: String) = fun Context.hasPermission(permission: String) =
ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED
fun String.wordCapitalize() : String {
val result = ArrayList<String>()
for (word in this.split(" "))
result.add(word.capitalize())
return result.joinToString(" ")
}

View File

@@ -104,6 +104,7 @@
android:id="@+id/main_searchview" android:id="@+id/main_searchview"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:floatingSearch_backgroundColor="?attr/colorSurface"
app:floatingSearch_searchBarMarginLeft="8dp" app:floatingSearch_searchBarMarginLeft="8dp"
app:floatingSearch_searchBarMarginRight="8dp" app:floatingSearch_searchBarMarginRight="8dp"
app:floatingSearch_searchBarMarginTop="8dp" app:floatingSearch_searchBarMarginTop="8dp"

View File

@@ -17,35 +17,99 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<LinearLayout 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/gallery_layout" android:id="@+id/gallery_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"> android:orientation="vertical">
<androidx.appcompat.widget.Toolbar <com.google.android.material.appbar.AppBarLayout
android:id="@+id/gallery_toolbar" android:id="@+id/gallery_toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content">
app:menu="@menu/gallery"
android:background="@color/colorPrimary" <com.google.android.material.appbar.CollapsingToolbarLayout
app:titleTextColor="@color/white" android:layout_width="match_parent"
android:minHeight="?attr/actionBarSize"/> android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp">
<ImageView
android:id="@+id/gallery_thumbnail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/gallery_title"/>
<TextView
android:id="@+id/gallery_title"
style="@style/TextAppearance.AppCompat.Headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:id="@+id/gallery_artist"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/gallery_title"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
<View
android:id="@+id/gallery_padding"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/gallery_artist"
app:layout_constraintBottom_toTopOf="@id/gallery_type"/>
<com.google.android.material.chip.Chip
android:id="@+id/gallery_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/gallery_thumbnail"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:id="@+id/gallery_contents"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
</androidx.core.widget.NestedScrollView>
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<ImageView
android:id="@+id/gallery_thumbnail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
android:contentDescription=""
android:padding="16dp"/>
<ProgressBar <ProgressBar
android:id="@+id/gallery_progressbar" android:id="@+id/gallery_progressbar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
@@ -55,14 +119,14 @@
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/> app:layout_constraintBottom_toBottomOf="parent"/>
<com.github.clans.fab.FloatingActionButton
android:id="@+id/gallery_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> <com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/gallery_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
app:layout_anchor="@id/gallery_toolbar"
app:layout_anchorGravity="bottom|end"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<TextView
android:id="@+id/gallery_details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.MaterialComponents.Body1"
android:textColor="@color/colorAccent"/>
<LinearLayout
android:id="@+id/gallery_details_contents"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Pupil, Hitomi.la viewer for Android
~ Copyright (C) 2019 tom5079
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ This program is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="8dp">
<TextView
style="@style/TextAppearance.MaterialComponents.Body2"
android:id="@+id/gallery_details_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingBottom="8dp"/>
<com.google.android.material.chip.ChipGroup
android:id="@+id/gallery_details_tags"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:chipSpacingVertical="8dp"/>
</LinearLayout>

View File

@@ -17,11 +17,7 @@
~ along with this program. If not, see <http://www.gnu.org/licenses/>. ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
--> -->
<com.google.android.material.chip.Chip <GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_height="wrap_content"
android:layout_width="wrap_content" android:columnCount="3"/>
android:layout_height="24dp"
app:chipIconSize="16dp"
app:chipStartPadding="8dp"
app:chipCornerRadius="100dp"/>

View File

@@ -87,8 +87,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/> app:layout_constraintTop_toBottomOf="@id/galleryblock_title"/>
@@ -99,8 +97,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_artist" app:layout_constraintTop_toBottomOf="@id/galleryblock_artist"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail"
app:layout_constraintEnd_toEndOf="parent"/> app:layout_constraintEnd_toEndOf="parent"/>
@@ -111,8 +107,6 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_series" app:layout_constraintTop_toBottomOf="@id/galleryblock_series"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" /> app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
@@ -123,8 +117,6 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:clickable="true"
android:focusable="true"
app:layout_constraintTop_toBottomOf="@id/galleryblock_type" app:layout_constraintTop_toBottomOf="@id/galleryblock_type"
app:layout_constraintBottom_toTopOf="@id/galleryblock_padding" app:layout_constraintBottom_toTopOf="@id/galleryblock_padding"
app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" /> app:layout_constraintStart_toEndOf="@id/galleryblock_thumbnail" />
@@ -145,6 +137,7 @@
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp"
app:chipSpacing="2dp"
app:layout_constraintTop_toBottomOf="@id/galleryblock_padding" app:layout_constraintTop_toBottomOf="@id/galleryblock_padding"
app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail" app:layout_constraintLeft_toRightOf="@id/galleryblock_thumbnail"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"

View File

@@ -88,4 +88,15 @@
<string name="ignore_update">無視</string> <string name="ignore_update">無視</string>
<string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string> <string name="lock_corrupted">ロックファイルが破損されています。Pupilを再再インストールしてください。</string>
<string name="update_no_permission">権限がないため自動アップデートを行えません。ホームページで直接ダウンロードしてください。</string> <string name="update_no_permission">権限がないため自動アップデートを行えません。ホームページで直接ダウンロードしてください。</string>
<string name="settings_dark_mode_title">ダークモード</string>
<string name="settings_dark_mode_summary">夜にシコりたい方々へ</string>
<string name="gallery_details">ギャラリー情報</string>
<string name="gallery_artists">アーティスト</string>
<string name="gallery_characters">キャラクター</string>
<string name="gallery_groups">グループ</string>
<string name="gallery_language">言語</string>
<string name="gallery_series">シリーズ</string>
<string name="gallery_tags">タグ</string>
<string name="gallery_thumbnails">サムネイル</string>
<string name="gallery_related">おすすめ</string>
</resources> </resources>

View File

@@ -88,4 +88,15 @@
<string name="ignore_update">무시</string> <string name="ignore_update">무시</string>
<string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string> <string name="lock_corrupted">잠금 파일이 손상되었습니다! 앱을 재설치 해 주시기 바랍니다.</string>
<string name="update_no_permission">권한이 부여되어 있지 않아 자동 업데이트를 진행할 수 없습니다. 홈페이지에서 직접 다운로드 받으시기 바랍니다.</string> <string name="update_no_permission">권한이 부여되어 있지 않아 자동 업데이트를 진행할 수 없습니다. 홈페이지에서 직접 다운로드 받으시기 바랍니다.</string>
<string name="settings_dark_mode_title">다크 모드</string>
<string name="settings_dark_mode_summary">딥 다크한 모오드</string>
<string name="gallery_details">갤러리 정보</string>
<string name="gallery_artists">작가</string>
<string name="gallery_characters">캐릭터</string>
<string name="gallery_groups">그룹</string>
<string name="gallery_language">언어</string>
<string name="gallery_series">시리즈</string>
<string name="gallery_tags">태그</string>
<string name="gallery_related">관련 갤러리</string>
<string name="gallery_thumbnails">미리보기</string>
</resources> </resources>

View File

@@ -8,4 +8,6 @@
<dimen name="activity_vertical_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="nav_header_vertical_spacing">8dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen>
<dimen name="nav_header_height">176dp</dimen> <dimen name="nav_header_height">176dp</dimen>
<dimen name="thumbnail_margin">8dp</dimen>
</resources> </resources>

View File

@@ -78,6 +78,16 @@
<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="search_hint_with_page">Search galleries</string>
<string name="gallery_details">Details</string>
<string name="gallery_thumbnails">Thumbnails</string>
<string name="gallery_related">Related Galleries</string>
<string name="gallery_artists">Artists</string>
<string name="gallery_groups">Groups</string>
<string name="gallery_language">Language</string>
<string name="gallery_series">Series</string>
<string name="gallery_characters">Characters</string>
<string name="gallery_tags">Tags</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>
<string name="galleryblock_language">Language: %1$s</string> <string name="galleryblock_language">Language: %1$s</string>
@@ -112,6 +122,8 @@
<string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string> <string name="settings_use_hiyobi_summary">Load images from hiyobi.me to improve loading speed (if available)</string>
<string name="settings_security_mode_title">Enable security mode</string> <string name="settings_security_mode_title">Enable security mode</string>
<string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string> <string name="settings_security_mode_summary">Enable security mode to make the screen invisible on recent app window</string>
<string name="settings_dark_mode_title">Dark mode</string>
<string name="settings_dark_mode_summary">Protect yourself against light attacks!</string>
<string name="settings_lock_none">None</string> <string name="settings_lock_none">None</string>
<string name="settings_lock_pattern">Pattern</string> <string name="settings_lock_pattern">Pattern</string>

View File

@@ -1,14 +1,14 @@
<resources> <resources>
<!-- Base application theme. --> <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar"> <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item> <item name="colorAccent">@color/colorAccent</item>
</style> </style>
<style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.Light.NoActionBar"> <style name="NoActionBarAppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<!-- Customize your theme here. --> <!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item>

View File

@@ -65,6 +65,11 @@
app:summary="@string/settings_security_mode_summary" app:summary="@string/settings_security_mode_summary"
app:defaultValue="true"/> app:defaultValue="true"/>
<SwitchPreference
app:key="dark_mode"
app:title="@string/settings_dark_mode_title"
app:summary="@string/settings_dark_mode_summary"/>
</PreferenceCategory> </PreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>

View File

@@ -18,7 +18,7 @@ buildscript {
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files // in the individual module build.gradle files
classpath 'io.fabric.tools:gradle:1.29.0' classpath 'io.fabric.tools:gradle:1.29.0'
classpath 'com.google.firebase:perf-plugin:1.2.1' classpath 'com.google.firebase:perf-plugin:1.3.0'
} }
} }

View File

@@ -14,7 +14,6 @@ dependencies {
sourceCompatibility = "7" sourceCompatibility = "7"
targetCompatibility = "7" targetCompatibility = "7"
buildscript { buildscript {
ext.kotlin_version = '1.3.31'
repositories { repositories {
mavenCentral() mavenCentral()
} }

View File

@@ -18,6 +18,7 @@ package xyz.quaver.hitomi
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.net.URL import java.net.URL
import java.net.URLDecoder
data class Gallery( data class Gallery(
val related: List<Int>, val related: List<Int>,
@@ -63,11 +64,11 @@ fun getGallery(galleryID: Int) : Gallery {
val characters = doc.select(".gallery-info a[href~=^/character/]").map { it.text() } val characters = doc.select(".gallery-info a[href~=^/character/]").map { it.text() }
val tags = doc.select(".gallery-info a[href~=^/tag/]").map { val tags = doc.select(".gallery-info a[href~=^/tag/]").map {
val href = it.attr("href") val href = URLDecoder.decode(it.attr("href"), "UTF-8")
href.slice(5 until href.indexOf('-')) href.slice(5 until href.indexOf('-'))
} }
val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/\\d+.+)',") val thumbnails = Regex("'(//tn.hitomi.la/smalltn/\\d+/.+)',")
.findAll(doc.select("script").last().html()) .findAll(doc.select("script").last().html())
.map { .map {
protocol + it.groups[1]!!.value protocol + it.groups[1]!!.value