diff --git a/app/build.gradle b/app/build.gradle
index c057f840..9b296214 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -94,13 +94,12 @@ dependencies {
implementation "com.google.android.material:material:1.3.0-alpha04"
- implementation "com.google.firebase:firebase-core:18.0.0"
- implementation "com.google.firebase:firebase-analytics:18.0.0"
- implementation "com.google.firebase:firebase-crashlytics:17.3.0"
- implementation "com.google.firebase:firebase-perf:19.0.10"
+ implementation platform("com.google.firebase:firebase-bom:26.1.0")
+ implementation "com.google.firebase:firebase-analytics-ktx"
+ implementation "com.google.firebase:firebase-crashlytics"
+ implementation "com.google.firebase:firebase-perf"
implementation "com.google.android.gms:play-services-oss-licenses:17.0.0"
- implementation "com.google.android.gms:play-services-mlkit-face-detection:16.1.1"
implementation "com.github.clans:fab:1.6.4"
@@ -123,7 +122,7 @@ dependencies {
implementation "ru.noties.markwon:core:3.1.0"
- implementation "xyz.quaver:libpupil:1.9.0"
+ implementation "xyz.quaver:libpupil:1.9.7"
implementation "xyz.quaver:documentfilex:0.4-alpha02"
implementation "xyz.quaver:floatingsearchview:1.0.7"
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index 7b1b98e0..63a3a4b6 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -23,12 +23,21 @@
-dontobfuscate
-keepattributes *Annotation*, InnerClasses
--dontnote kotlinx.serialization.SerializationKt
--keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; } # <-- change package name to your app's
--keepclassmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
+-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
+
+# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
+-keepclassmembers class kotlinx.serialization.json.** {
*** Companion;
}
--keepclasseswithmembers class xyz.quaver.pupil.** { # <-- change package name to your app's
+-keepclasseswithmembers class kotlinx.serialization.json.** {
+ kotlinx.serialization.KSerializer serializer(...);
+}
+
+-keep,includedescriptorclasses class xyz.quaver.**$$serializer { *; }
+-keepclassmembers class xyz.quaver.** {
+ *** Companion;
+}
+-keepclasseswithmembers class xyz.quaver.** {
kotlinx.serialization.KSerializer serializer(...);
}
-keep class xyz.quaver.pupil.ui.fragment.ManageFavoritesFragment
diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
index b021026c..26ecdbf8 100644
--- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
+++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt
@@ -20,6 +20,7 @@
package xyz.quaver.pupil
+import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Test
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d5e1e304..3675b4b6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,15 +6,11 @@
-
-
+
+
-
-
-
-
- binding.image.updateLayoutParams {
- dimensionRatio = "${imageInfo.width}:${imageInfo.height}"
- }
- }
- })
- setImageShownCallback(object : ImageShownCallback {
- override fun onMainImageShown() {
- binding.image.mainView.let { v ->
- when (v) {
- is SubsamplingScaleImageView ->
- if (!isFullScreen) binding.image.layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
- }
- }
- }
-
- override fun onThumbnailShown() {}
- })
-
setFailureImage(ContextCompat.getDrawable(itemView.context, R.drawable.image_broken_variant))
setOnClickListener {
onItemClickListener?.invoke()
@@ -164,87 +134,4 @@ class ReaderAdapter(
holder.clear()
}
-}
-
-class FrescoImageViewFactory : ImageViewFactory() {
- var updateView: ((ImageInfo) -> Unit)? = null
-
- override fun createAnimatedImageView(
- context: Context, imageType: Int,
- initScaleType: Int
- ): View {
- val view = SimpleDraweeView(context)
- view.hierarchy.actualImageScaleType = scaleType(initScaleType)
- return view
- }
-
- override fun loadAnimatedContent(
- view: View, imageType: Int,
- imageFile: File
- ) {
- if (view is SimpleDraweeView) {
- val controller: DraweeController = Fresco.newDraweeControllerBuilder()
- .setUri(Uri.parse("file://" + imageFile.absolutePath))
- .setAutoPlayAnimations(true)
- .setControllerListener(object: BaseControllerListener() {
- override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
- imageInfo?.let { updateView?.invoke(it) }
- }
-
- override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
- imageInfo?.let { updateView?.invoke(it) }
- }
- })
- .build()
- view.controller = controller
- }
- }
-
- override fun createThumbnailView(
- context: Context,
- scaleType: ImageView.ScaleType, willLoadFromNetwork: Boolean
- ): View {
- return if (willLoadFromNetwork) {
- val thumbnailView = SimpleDraweeView(context)
- thumbnailView.hierarchy.actualImageScaleType = scaleType(scaleType)
- thumbnailView
- } else {
- super.createThumbnailView(context, scaleType, false)
- }
- }
-
- override fun loadThumbnailContent(view: View, thumbnail: Uri) {
- if (view is SimpleDraweeView) {
- val controller: DraweeController = Fresco.newDraweeControllerBuilder()
- .setUri(thumbnail)
- .build()
- view.controller = controller
- }
- }
-
- private fun scaleType(value: Int): ScalingUtils.ScaleType {
- return when (value) {
- BigImageView.INIT_SCALE_TYPE_CENTER -> ScalingUtils.ScaleType.CENTER
- BigImageView.INIT_SCALE_TYPE_CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
- BigImageView.INIT_SCALE_TYPE_CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
- BigImageView.INIT_SCALE_TYPE_FIT_END -> ScalingUtils.ScaleType.FIT_END
- BigImageView.INIT_SCALE_TYPE_FIT_START -> ScalingUtils.ScaleType.FIT_START
- BigImageView.INIT_SCALE_TYPE_FIT_XY -> ScalingUtils.ScaleType.FIT_XY
- BigImageView.INIT_SCALE_TYPE_FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
- else -> ScalingUtils.ScaleType.FIT_CENTER
- }
- }
-
- private fun scaleType(scaleType: ImageView.ScaleType): ScalingUtils.ScaleType {
- return when (scaleType) {
- ImageView.ScaleType.CENTER -> ScalingUtils.ScaleType.CENTER
- ImageView.ScaleType.CENTER_CROP -> ScalingUtils.ScaleType.CENTER_CROP
- ImageView.ScaleType.CENTER_INSIDE -> ScalingUtils.ScaleType.CENTER_INSIDE
- ImageView.ScaleType.FIT_END -> ScalingUtils.ScaleType.FIT_END
- ImageView.ScaleType.FIT_START -> ScalingUtils.ScaleType.FIT_START
- ImageView.ScaleType.FIT_XY -> ScalingUtils.ScaleType.FIT_XY
- ImageView.ScaleType.FIT_CENTER -> ScalingUtils.ScaleType.FIT_CENTER
- else -> ScalingUtils.ScaleType.FIT_CENTER
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt
index af341930..b852ed47 100644
--- a/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt
+++ b/app/src/main/java/xyz/quaver/pupil/adapters/SearchResultsAdapter.kt
@@ -21,18 +21,19 @@ package xyz.quaver.pupil.adapters
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
-import android.net.Uri
+import android.graphics.drawable.Animatable
import android.view.LayoutInflater
+import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.daimajia.swipe.SwipeLayout
import com.daimajia.swipe.adapters.RecyclerSwipeAdapter
import com.daimajia.swipe.interfaces.SwipeAdapterInterface
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
+import com.facebook.drawee.backends.pipeline.Fresco
+import com.facebook.drawee.controller.BaseControllerListener
+import com.facebook.imagepipeline.image.ImageInfo
+import kotlinx.coroutines.*
import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.SearchResultItemBinding
import xyz.quaver.pupil.sources.SearchResult
@@ -40,21 +41,25 @@ import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.ui.view.ProgressCardView
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager
-import xyz.quaver.pupil.util.wordCapitalize
+import kotlin.time.ExperimentalTime
class SearchResultsAdapter(private val results: List) : RecyclerSwipeAdapter(), SwipeAdapterInterface {
- val onChipClickedHandler = ArrayList<((Tag) -> Unit)>()
+ var onChipClickedHandler: ((Tag) -> Unit)? = null
var onDownloadClickedHandler: ((String) -> Unit)? = null
var onDeleteClickedHandler: ((String) -> Unit)? = null
+ // TODO: migrate to viewBinding
+ val progressUpdateScope = CoroutineScope(Dispatchers.Main + Job())
+
inner class ViewHolder(private val binding: SearchResultItemBinding) : RecyclerView.ViewHolder(binding.root) {
var itemID: String = ""
- var update = true
+
+ private var bindJob: Job? = null
init {
- CoroutineScope(Dispatchers.Main).launch {
- while (update) {
+ progressUpdateScope.launch {
+ while (true) {
updateProgress()
delay(1000)
}
@@ -92,9 +97,11 @@ class SearchResultsAdapter(private val results: List) : RecyclerSw
override fun onHandRelease(layout: SwipeLayout?, xvel: Float, yvel: Float) {}
override fun onUpdate(layout: SwipeLayout?, leftOffset: Int, topOffset: Int) {}
})
+
+ binding.tagGroup.onClickListener = onChipClickedHandler
}
- fun updateProgress() = CoroutineScope(Dispatchers.Main).launch {
+ private fun updateProgress() = CoroutineScope(Dispatchers.Main).launch {
with (itemView as ProgressCardView) {
val imageList = Cache.getInstance(context, itemID).metadata.imageList
@@ -118,32 +125,111 @@ class SearchResultsAdapter(private val results: List) : RecyclerSw
}
}
+ val controllerListener = object: BaseControllerListener() {
+ override fun onIntermediateImageSet(id: String?, imageInfo: ImageInfo?) {
+ imageInfo?.let {
+ binding.thumbnail.aspectRatio = it.width / it.height.toFloat()
+ }
+ }
+
+ override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
+ imageInfo?.let {
+ binding.thumbnail.aspectRatio = it.width / it.height.toFloat()
+ }
+ }
+ }
fun bind(result: SearchResult) {
+ bindJob?.cancel()
itemID = result.id
- binding.thumbnail.ssiv?.recycle()
- binding.thumbnail.showImage(Uri.parse(result.thumbnail))
+ binding.thumbnail.controller = Fresco.newDraweeControllerBuilder()
+ .setUri(result.thumbnail)
+ .setControllerListener(controllerListener)
+ .build()
updateProgress()
binding.title.text = result.title
binding.idView.text = result.id
- binding.artist.text = result.artists.joinToString { it.wordCapitalize() }
+
+ binding.artist.visibility = if (result.artists.isEmpty()) View.GONE else View.VISIBLE
+ binding.artist.text = result.artists
+
+ with (binding.tagGroup) {
+ tags.clear()
+ tags.addAll(result.tags.map {
+ Tag.parse(it)
+ })
+ refresh()
+ }
+
+ binding.pagecount.text = "-"
+
+ bindJob = MainScope().launch {
+ val extra = result.extra.mapValues {
+ async(Dispatchers.IO) {
+ kotlin.runCatching { withTimeout(1000) {
+ it.value.invoke()
+ } }.getOrNull()
+ }
+ }
+
+ launch {
+ val extraType = listOf(
+ SearchResult.ExtraType.SERIES,
+ SearchResult.ExtraType.TYPE,
+ SearchResult.ExtraType.LANGUAGE
+ )
+
+ binding.extra.text = extra.entries.filter { it.key in extraType }.fold(StringBuilder()) { res, entry ->
+ entry.value.await().let {
+ if (!it.isNullOrEmpty()) {
+ res.append(
+ itemView.context.getString(
+ SearchResult.extraTypeMap[entry.key] ?: error(""),
+ it
+ )
+ )
+ res.append('\n')
+ }
+ res
+ }
+ }
+ }
+
+ launch {
+ extra[SearchResult.ExtraType.PAGECOUNT]?.await()?.let {
+ binding.pagecount.text =
+ itemView.context.getString(
+ SearchResult.extraTypeMap[SearchResult.ExtraType.PAGECOUNT] ?: error(""),
+ it
+ )
+ }
+ }
+
+ launch {
+ extra[SearchResult.ExtraType.GROUP]?.await().let {
+ if (!it.isNullOrEmpty())
+ binding.artist.text = itemView.context.getString(
+ R.string.galleryblock_artist_with_group,
+ result.artists,
+ it
+ )
+ }
+ }
+ }
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
+ @ExperimentalTime
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
mItemManger.bindView(holder.itemView, position)
holder.bind(results[position])
}
- override fun onViewDetachedFromWindow(holder: ViewHolder) {
- holder.update = false
- }
-
override fun getItemCount(): Int = results.size
override fun getSwipeLayoutResourceId(position: Int): Int = R.id.swipe_layout
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt
index f7bf95c7..e1a5f0fb 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt
@@ -18,20 +18,39 @@
package xyz.quaver.pupil.sources
-import com.google.android.gms.vision.L
+import kotlinx.coroutines.channels.Channel
+import xyz.quaver.pupil.R
import kotlin.reflect.KClass
-interface SearchResult {
- val id: String
- val title: String
- val thumbnail: String
- val artists: List
+data class SearchResult(
+ val id: String,
+ val title: String,
+ val thumbnail: String,
+ val artists: String,
+ val extra: Map String>,
+ val tags: List
+) {
+ enum class ExtraType {
+ GROUP,
+ CHARACTER,
+ SERIES,
+ TYPE,
+ LANGUAGE,
+ PAGECOUNT
+ }
+
+ companion object {
+ val extraTypeMap = mapOf(
+ ExtraType.SERIES to R.string.galleryblock_series,
+ ExtraType.TYPE to R.string.galleryblock_type,
+ ExtraType.LANGUAGE to R.string.galleryblock_language,
+ ExtraType.PAGECOUNT to R.string.galleryblock_pagecount
+ )
+ }
}
-// Might be better to use channel on Query_Result
-interface Source, Query_Result: SearchResult> {
- val querySortModeClass: KClass
- val queryResultClass: KClass
+interface Source> {
+ val querySortModeClass: KClass?
- suspend fun query(query: String, range: IntRange, sortMode: Query_SortMode? = null) : Pair, Int>
+ suspend fun query(query: String, range: IntRange, sortMode: Query_SortMode? = null) : Pair, Int>
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
index b7516fdc..98d9b097 100644
--- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hitomi.kt
@@ -18,35 +18,31 @@
package xyz.quaver.pupil.sources.hitomi
-import kotlinx.coroutines.yield
-import xyz.quaver.hitomi.doSearch
-import xyz.quaver.hitomi.getGalleryBlock
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
+import xyz.quaver.hitomi.*
+import xyz.quaver.pupil.sources.SearchResult
+import xyz.quaver.pupil.sources.SearchResult.ExtraType
import xyz.quaver.pupil.sources.Source
-import kotlin.math.min
+import xyz.quaver.pupil.util.wordCapitalize
import kotlin.math.max
+import kotlin.math.min
-class Hitomi : Source {
+class Hitomi : Source {
override val querySortModeClass = SortMode::class
- override val queryResultClass = SearchResult::class
enum class SortMode {
NEWEST,
POPULAR
}
- data class SearchResult(
- override val id: String,
- override val title: String,
- override val thumbnail: String,
- override val artists: List,
- ) : xyz.quaver.pupil.sources.SearchResult
-
var cachedQuery: String? = null
+ var cachedSortMode: SortMode? = null
val cache = mutableListOf()
- override suspend fun query(query: String, range: IntRange, sortMode: SortMode?): Pair, Int> {
- if (cachedQuery != query) {
+ override suspend fun query(query: String, range: IntRange, sortMode: SortMode?): Pair, Int> {
+ if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) {
cachedQuery = null
cache.clear()
yield()
@@ -57,17 +53,85 @@ class Hitomi : Source {
cachedQuery = query
}
+ val channel = Channel()
val sanitizedRange = max(0, range.first) .. min(range.last, cache.size-1)
- return Pair(cache.slice(sanitizedRange).map {
- getGalleryBlock(it).let { gallery ->
- SearchResult(
- gallery.id.toString(),
- gallery.title,
- gallery.thumbnails.first(),
- gallery.artists
- )
+
+ CoroutineScope(Dispatchers.IO).launch {
+ cache.slice(sanitizedRange).map {
+ async {
+ getGalleryBlock(it)
+ }
+ }.forEach {
+ kotlin.runCatching {
+ yield()
+ channel.send(transform(it.await()))
+ }.onFailure {
+ channel.close()
+ }
}
- }, cache.size)
+
+ channel.close()
+ }
+
+ return Pair(channel, cache.size)
+ }
+
+ companion object {
+ val languageMap = mapOf(
+ "indonesian" to "Bahasa Indonesia",
+ "catalan" to "català",
+ "cebuano" to "Cebuano",
+ "czech" to "Čeština",
+ "danish" to "Dansk",
+ "german" to "Deutsch",
+ "estonian" to "eesti",
+ "english" to "English",
+ "spanish" to "Español",
+ "esperanto" to "Esperanto",
+ "french" to "Français",
+ "italian" to "Italiano",
+ "latin" to "Latina",
+ "hungarian" to "magyar",
+ "dutch" to "Nederlands",
+ "norwegian" to "norsk",
+ "polish" to "polski",
+ "portuguese" to "Português",
+ "romanian" to "română",
+ "albanian" to "shqip",
+ "slovak" to "Slovenčina",
+ "finnish" to "Suomi",
+ "swedish" to "Svenska",
+ "tagalog" to "Tagalog",
+ "vietnamese" to "tiếng việt",
+ "turkish" to "Türkçe",
+ "greek" to "Ελληνικά",
+ "mongolian" to "Монгол",
+ "russian" to "Русский",
+ "ukrainian" to "Українська",
+ "hebrew" to "עברית",
+ "arabic" to "العربية",
+ "persian" to "فارسی",
+ "thai" to "ไทย",
+ "korean" to "한국어",
+ "chinese" to "中文",
+ "japanese" to "日本語"
+ )
+
+ fun transform(galleryBlock: GalleryBlock): SearchResult =
+ SearchResult(
+ galleryBlock.id.toString(),
+ galleryBlock.title,
+ galleryBlock.thumbnails.first(),
+ galleryBlock.artists.joinToString { it.wordCapitalize() },
+ mapOf(
+ ExtraType.GROUP to { getGallery(galleryBlock.id).groups.joinToString { it.wordCapitalize() } },
+ ExtraType.SERIES to { galleryBlock.series.joinToString { it.wordCapitalize() } },
+ ExtraType.TYPE to { galleryBlock.type.wordCapitalize() },
+ ExtraType.LANGUAGE to { languageMap[galleryBlock.language] ?: galleryBlock.language },
+ ExtraType.PAGECOUNT to { getGalleryInfo(galleryBlock.id).files.size.toString() }
+ ),
+ galleryBlock.relatedTags
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hiyobi.kt b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hiyobi.kt
new file mode 100644
index 00000000..cd62be61
--- /dev/null
+++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hiyobi.kt
@@ -0,0 +1,69 @@
+/*
+ * Pupil, Hitomi.la viewer for Android
+ * Copyright (C) 2020 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 .
+ */
+
+package xyz.quaver.pupil.sources.hitomi
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.launch
+import xyz.quaver.hiyobi.*
+import xyz.quaver.pupil.sources.SearchResult
+import xyz.quaver.pupil.sources.Source
+import xyz.quaver.pupil.util.wordCapitalize
+
+class Hiyobi : Source> {
+ override val querySortModeClass = null
+
+ override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>?): Pair, Int> {
+ val channel = Channel()
+
+ val (results, total) = if (query.isEmpty())
+ list(range)
+ else
+ search(query, range)
+
+ CoroutineScope(Dispatchers.Unconfined).launch {
+ results.forEach {
+ channel.send(transform(it))
+ }
+
+ channel.close()
+ }
+
+ return Pair(channel, total)
+ }
+
+ companion object {
+ fun transform(galleryBlock: GalleryBlock): SearchResult =
+ SearchResult(
+ galleryBlock.id,
+ galleryBlock.title,
+ "https://cdn.$hiyobi/tn/${galleryBlock.id}.jpg",
+ galleryBlock.artists.joinToString { it.value.wordCapitalize() },
+ mapOf(
+ SearchResult.ExtraType.CHARACTER to { galleryBlock.characters.joinToString { it.value.wordCapitalize() } },
+ SearchResult.ExtraType.SERIES to { galleryBlock.parodys.joinToString { it.value.wordCapitalize() } },
+ SearchResult.ExtraType.TYPE to { galleryBlock.type.name.wordCapitalize() },
+ SearchResult.ExtraType.PAGECOUNT to { getGalleryInfo(galleryBlock.id).files.size.toString() }
+ ),
+ galleryBlock.tags.map { it.value }
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
index 4b588937..2b04d51c 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt
@@ -38,6 +38,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.navigation.NavigationView
import com.google.android.material.snackbar.Snackbar
import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.Channel
import xyz.quaver.floatingsearchview.FloatingSearchView
import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion
import xyz.quaver.floatingsearchview.util.view.MenuView
@@ -50,6 +51,7 @@ import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.sources.SearchResult
import xyz.quaver.pupil.sources.Source
import xyz.quaver.pupil.sources.hitomi.Hitomi
+import xyz.quaver.pupil.sources.hitomi.Hiyobi
import xyz.quaver.pupil.types.*
import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment
import xyz.quaver.pupil.ui.dialog.GalleryDialog
@@ -62,6 +64,7 @@ import xyz.quaver.pupil.util.downloader.DownloadManager
import xyz.quaver.pupil.util.restore
import java.util.regex.Pattern
import kotlin.math.*
+import kotlin.random.Random
class MainActivity :
BaseActivity(),
@@ -80,10 +83,10 @@ class MainActivity :
private var queryStack = mutableListOf()
@Suppress("UNCHECKED_CAST")
- private var source: Source, SearchResult> = Hitomi() as Source, SearchResult>
+ private var source: Source> = Hiyobi() as Source>
private var sortMode = Hitomi.SortMode.NEWEST
- private var searchJob: Deferred, Int>>? = null
+ private var searchJob: Deferred, Int>>? = null
private var totalItems = 0
private var currentPage = 1
@@ -114,6 +117,12 @@ class MainActivity :
initView()
}
+ override fun onDestroy() {
+ super.onDestroy()
+
+ (binding.contents.recyclerview.adapter as SearchResultsAdapter).progressUpdateScope.cancel()
+ }
+
@OptIn(ExperimentalStdlibApi::class)
override fun onBackPressed() {
when {
@@ -215,25 +224,29 @@ class MainActivity :
with(binding.contents.randomFab) {
setImageResource(R.drawable.shuffle_variant)
setOnClickListener {
- runBlocking {
- withTimeoutOrNull(100) {
- searchJob?.await()
- }
- }.let {
- if (it?.first?.isEmpty() == false) {
- val random = it.first.random()
+ if (totalItems > 0)
+ CoroutineScope(Dispatchers.IO).launch {
+ val random = Random.Default.nextInt(totalItems)
- GalleryDialog(this@MainActivity, random.id).apply {
- onChipClickedHandler.add {
- query = it.toQuery()
- currentPage = 1
+ val randomResult =
+ source.query(
+ query + Preferences["default_query", ""],
+ random .. random,
+ sortMode
+ ).first.receive()
- query()
- dismiss()
- }
- }.show()
+ launch(Dispatchers.Main) {
+ GalleryDialog(this@MainActivity, randomResult.id).apply {
+ onChipClickedHandler.add {
+ query = it.toQuery()
+ currentPage = 1
+
+ query()
+ dismiss()
+ }
+ }.show()
+ }
}
- }
}
}
@@ -273,11 +286,6 @@ class MainActivity :
// disable pageturn until the contents are loaded
setCurrentPage(1, false)
- ViewCompat.animate(binding.contents.searchview)
- .setDuration(100)
- .setInterpolator(DecelerateInterpolator())
- .translationY(0F)
-
query()
}
@@ -306,9 +314,9 @@ class MainActivity :
private fun setupRecyclerView() {
with(binding.contents.recyclerview) {
adapter = SearchResultsAdapter(searchResults).apply {
- onChipClickedHandler.add {
+ onChipClickedHandler = {
query = it.toQuery()
- currentPage = 0
+ currentPage = 1
query()
}
@@ -353,7 +361,7 @@ class MainActivity :
GalleryDialog(this@MainActivity, result.id).apply {
onChipClickedHandler.add {
query = it.toQuery()
- currentPage = 0
+ currentPage = 1
query()
dismiss()
@@ -535,6 +543,11 @@ class MainActivity :
binding.contents.noresult.visibility = View.INVISIBLE
binding.contents.progressbar.show()
+
+ ViewCompat.animate(binding.contents.searchview)
+ .setDuration(100)
+ .setInterpolator(DecelerateInterpolator())
+ .translationY(0F)
}
private fun query() {
val perPage = Preferences["per_page", "25"].toInt()
@@ -550,22 +563,21 @@ class MainActivity :
sortMode
)
}.also {
- val results: List
-
it.await().let { r ->
- results = r.first
totalItems = r.second
+ r.first
+ }.let { channel ->
+ binding.contents.progressbar.hide()
+ binding.contents.swipePageTurnView.setCurrentPage(currentPage, totalItems > currentPage*perPage)
+
+ for (result in channel) {
+ searchResults.add(result)
+ binding.contents.recyclerview.adapter?.notifyItemInserted(searchResults.size)
+ }
}
- binding.contents.progressbar.hide()
- binding.contents.swipePageTurnView.setCurrentPage(currentPage, totalItems > currentPage*perPage)
-
- if (results.isEmpty()) {
+ if (searchResults.isEmpty())
binding.contents.noresult.visibility = View.VISIBLE
- } else {
- searchResults.addAll(results)
- binding.contents.recyclerview.adapter?.notifyDataSetChanged()
- }
}
}
}
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
index 6570ce5e..7c1bc4cf 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt
@@ -18,22 +18,14 @@
package xyz.quaver.pupil.ui
-import android.Manifest
import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
-import android.content.pm.PackageManager
import android.graphics.drawable.Animatable
import android.graphics.drawable.Drawable
-import android.os.Build
import android.os.Bundle
import android.os.IBinder
import android.view.*
-import android.view.animation.Animation
-import android.view.animation.AnticipateInterpolator
-import android.view.animation.OvershootInterpolator
-import android.view.animation.TranslateAnimation
-import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
@@ -43,7 +35,6 @@ import androidx.vectordrawable.graphics.drawable.Animatable2Compat
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import com.google.android.material.snackbar.Snackbar
import com.google.firebase.crashlytics.FirebaseCrashlytics
-import com.google.mlkit.vision.face.Face
import com.qtalk.recyclerviewfastscroller.RecyclerViewFastScroller
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -56,11 +47,8 @@ import xyz.quaver.pupil.databinding.ReaderActivityBinding
import xyz.quaver.pupil.favorites
import xyz.quaver.pupil.services.DownloadService
import xyz.quaver.pupil.util.Preferences
-import xyz.quaver.pupil.util.camera
-import xyz.quaver.pupil.util.closeCamera
import xyz.quaver.pupil.util.downloader.Cache
import xyz.quaver.pupil.util.downloader.DownloadManager
-import xyz.quaver.pupil.util.startCamera
class ReaderActivity : BaseActivity() {
@@ -95,26 +83,6 @@ class ReaderActivity : BaseActivity() {
private val snapHelper = PagerSnapHelper()
private var menu: Menu? = null
- private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
- if (isGranted)
- toggleCamera()
- else
- AlertDialog.Builder(this)
- .setTitle(R.string.error)
- .setMessage(R.string.camera_denied)
- .setPositiveButton(android.R.string.ok) { _, _ ->}
- .show()
- }
-
- enum class Eye {
- LEFT,
- RIGHT
- }
-
- private var cameraEnabled = false
- private var eyeType: Eye? = null
- private var eyeTime: Long = 0L
-
private lateinit var binding: ReaderActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
@@ -217,14 +185,10 @@ class ReaderActivity : BaseActivity() {
super.onResume()
bindService(Intent(this, DownloadService::class.java), conn, BIND_AUTO_CREATE)
-
- if (cameraEnabled)
- startCamera(this, cameraCallback)
}
override fun onPause() {
super.onPause()
- closeCamera()
if (downloader != null)
unbindService(conn)
@@ -391,26 +355,6 @@ class ReaderActivity : BaseActivity() {
}
}
- with(binding.autoFab) {
- setImageResource(R.drawable.eye_white)
- setOnClickListener {
- when {
- ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED -> {
- toggleCamera()
- }
- Build.VERSION.SDK_INT >= 23 && shouldShowRequestPermissionRationale(Manifest.permission.CAMERA) -> {
- AlertDialog.Builder(this@ReaderActivity)
- .setTitle(R.string.warning)
- .setMessage(R.string.camera_denied)
- .setPositiveButton(android.R.string.ok) { _, _ ->}
- .show()
- }
- else ->
- requestPermissionLauncher.launch(Manifest.permission.CAMERA)
- }
- }
- }
-
with(binding.fullscreenFab) {
setImageResource(R.drawable.ic_fullscreen)
setOnClickListener {
@@ -496,120 +440,4 @@ class ReaderActivity : BaseActivity() {
}
}
}
-
- val cameraCallback: (List) -> Unit = callback@{ faces ->
- binding.eyeCard.dot.let {
- it.visibility = View.VISIBLE
- CoroutineScope(Dispatchers.Main).launch {
- delay(50)
- it.visibility = View.INVISIBLE
- }
- }
-
- if (faces.size != 1)
- ContextCompat.getDrawable(this, R.drawable.eye_off).let {
- with(binding.eyeCard) {
- leftEye.setImageDrawable(it)
- rightEye.setImageDrawable(it)
- }
-
- return@callback
- }
-
- val (left, right) = Pair(
- faces[0].rightEyeOpenProbability?.let { it > 0.4 } == true,
- faces[0].leftEyeOpenProbability?.let { it > 0.4 } == true
- )
-
- with(binding.eyeCard) {
- leftEye.setImageDrawable(
- ContextCompat.getDrawable(
- leftEye.context,
- if (left) R.drawable.eye else R.drawable.eye_closed
- )
- )
- rightEye.setImageDrawable(
- ContextCompat.getDrawable(
- rightEye.context,
- if (right) R.drawable.eye else R.drawable.eye_closed
- )
- )
- }
-
- when {
- // Both closed / opened
- !left.xor(right) -> {
- eyeType = null
- eyeTime = 0L
- }
- !left -> {
- if (eyeType != Eye.LEFT) {
- eyeType = Eye.LEFT
- eyeTime = System.currentTimeMillis()
- }
- }
- !right -> {
- if (eyeType != Eye.RIGHT) {
- eyeType = Eye.RIGHT
- eyeTime = System.currentTimeMillis()
- }
- }
- }
-
- if (eyeType != null && System.currentTimeMillis() - eyeTime > 100) {
- (binding.recyclerview.layoutManager as LinearLayoutManager).let {
- it.scrollToPositionWithOffset(when(eyeType!!) {
- Eye.RIGHT -> {
- if (it.reverseLayout) currentPage - 2 else currentPage
- }
- Eye.LEFT -> {
- if (it.reverseLayout) currentPage else currentPage - 2
- }
- }, 0)
- }
-
- eyeTime = System.currentTimeMillis() + 500
- }
- }
-
- private fun toggleCamera() {
- val eyes = binding.eyeCard.root
- when (camera) {
- null -> {
- binding.autoFab.labelText = getString(R.string.reader_fab_auto_cancel)
- binding.autoFab.setImageResource(R.drawable.eye_off_white)
- eyes.apply {
- visibility = View.VISIBLE
- TranslateAnimation(0F, 0F, -100F, 0F).apply {
- duration = 500
- fillAfter = false
- interpolator = OvershootInterpolator()
- }.let { startAnimation(it) }
- }
- startCamera(this, cameraCallback)
- cameraEnabled = true
- }
- else -> {
- binding.autoFab.labelText = getString(R.string.reader_fab_auto)
- binding.autoFab.setImageResource(R.drawable.eye_white)
- eyes.apply {
- TranslateAnimation(0F, 0F, 0F, -100F).apply {
- duration = 500
- fillAfter = false
- interpolator = AnticipateInterpolator()
- setAnimationListener(object: Animation.AnimationListener {
- override fun onAnimationStart(p0: Animation?) {}
- override fun onAnimationRepeat(p0: Animation?) {}
-
- override fun onAnimationEnd(p0: Animation?) {
- eyes.visibility = View.GONE
- }
- })
- }.let { startAnimation(it) }
- }
- closeCamera()
- cameraEnabled = false
- }
- }
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt
similarity index 73%
rename from app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt
rename to app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt
index cb73f790..eeb76ecc 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt
@@ -19,24 +19,21 @@
package xyz.quaver.pupil.ui.dialog
import android.app.Dialog
-import android.content.Context
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.ArrayAdapter
import androidx.appcompat.app.AlertDialog
+import androidx.fragment.app.DialogFragment
import xyz.quaver.pupil.R
import xyz.quaver.pupil.databinding.DefaultQueryDialogBinding
+import xyz.quaver.pupil.sources.hitomi.Hitomi
import xyz.quaver.pupil.types.Tags
import xyz.quaver.pupil.util.Preferences
-class DefaultQueryDialog(context : Context) : AlertDialog(context) {
-
- private val languages = context.resources.getStringArray(R.array.languages).map {
- it.split("|").let { split ->
- Pair(split[0], split[1])
- }
- }.toMap()
+class DefaultQueryDialogFragment() : DialogFragment() {
+ // TODO languageMap
+ private val languages = Hitomi.languageMap
private val reverseLanguages = languages.entries.associate { (k, v) -> v to k }
private val excludeBL = "-male:yaoi"
@@ -45,40 +42,46 @@ class DefaultQueryDialog(context : Context) : AlertDialog(context) {
var onPositiveButtonClickListener : ((Tags) -> (Unit))? = null
- private lateinit var binding: DefaultQueryDialogBinding
+ private var _binding: DefaultQueryDialogBinding? = null
+ private val binding get() = _binding!!
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- setTitle(R.string.default_query_dialog_title)
- binding = DefaultQueryDialogBinding.inflate(layoutInflater)
- setView(binding.root)
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ _binding = DefaultQueryDialogBinding.inflate(layoutInflater)
initView()
- setButton(Dialog.BUTTON_POSITIVE, context.getString(android.R.string.ok)) { _, _ ->
- val newTags = Tags.parse(binding.edittext.text.toString())
+ return AlertDialog.Builder(requireContext()).apply {
+ setTitle(R.string.default_query_dialog_title)
+ setView(binding.root)
+ setPositiveButton(android.R.string.ok) { _, _ ->
+ val newTags = Tags.parse(binding.edittext.text.toString())
- with(binding.languageSelector) {
- if (selectedItemPosition != 0)
- newTags.add("language:${reverseLanguages[selectedItem]}")
+ with(binding.languageSelector) {
+ if (selectedItemPosition != 0)
+ newTags.add("language:${reverseLanguages[selectedItem]}")
+ }
+
+ if (binding.BLCheckbox.isChecked)
+ newTags.add(excludeBL)
+
+ if (binding.guroCheckbox.isChecked)
+ excludeGuro.forEach { tag ->
+ newTags.add(tag)
+ }
+
+ if (binding.loliCheckbox.isChecked)
+ excludeLoli.forEach { tag ->
+ newTags.add(tag)
+ }
+
+ onPositiveButtonClickListener?.invoke(newTags)
}
+ }.create()
+ }
- if (binding.BLCheckbox.isChecked)
- newTags.add(excludeBL)
-
- if (binding.guroCheckbox.isChecked)
- excludeGuro.forEach { tag ->
- newTags.add(tag)
- }
-
- if (binding.loliCheckbox.isChecked)
- excludeLoli.forEach { tag ->
- newTags.add(tag)
- }
-
- onPositiveButtonClickListener?.invoke(newTags)
- }
+ override fun onDestroy() {
+ super.onDestroy()
+ _binding = null
}
private fun initView() {
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
index fe935813..c4249c85 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/GalleryDialog.kt
@@ -208,7 +208,7 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial
val galleries = mutableListOf()
val adapter = SearchResultsAdapter(galleries).apply {
- onChipClickedHandler.add { tag ->
+ onChipClickedHandler = { tag ->
this@GalleryDialog.onChipClickedHandler.forEach { handler ->
handler.invoke(tag)
}
@@ -218,7 +218,7 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial
GalleryDialogDetailsBinding.inflate(layoutInflater, binding.contents, true).apply {
type.setText(R.string.gallery_related)
- RecyclerView(context).apply {
+ contents.addView(RecyclerView(context).apply {
layoutManager = LinearLayoutManager(context)
this.adapter = adapter
@@ -238,19 +238,12 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial
true
}
}
- }
+ })
CoroutineScope(Dispatchers.IO).launch {
gallery.related.forEach { galleryID ->
Cache.getInstance(context, galleryID.toString()).getGalleryBlock()?.let {
- galleries.add(
- Hitomi.SearchResult(
- it.id.toString(),
- it.title,
- it.thumbnails.first(),
- it.artists
- )
- )
+ galleries.add(Hitomi.transform(it))
}
withContext(Dispatchers.Main) {
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
index 29d4f625..d852a06c 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/fragment/SettingsFragment.kt
@@ -85,12 +85,12 @@ class SettingsFragment :
DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog")
}
"default_query" -> {
- DefaultQueryDialog(requireContext()).apply {
+ DefaultQueryDialogFragment().apply {
onPositiveButtonClickListener = { newTags ->
Preferences["default_query"] = newTags.toString()
summary = newTags.toString()
}
- }.show()
+ }.show(parentFragmentManager, "Default Query Dialog")
}
"app_lock" -> {
val intent = Intent(requireContext(), LockActivity::class.java).apply {
diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/TagChip.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/TagChip.kt
index 917979da..6de28c78 100644
--- a/app/src/main/java/xyz/quaver/pupil/ui/view/TagChip.kt
+++ b/app/src/main/java/xyz/quaver/pupil/ui/view/TagChip.kt
@@ -24,6 +24,7 @@ import androidx.core.content.ContextCompat
import com.google.android.material.chip.Chip
import xyz.quaver.pupil.R
import xyz.quaver.pupil.favoriteTags
+import xyz.quaver.pupil.sources.hitomi.Hitomi
import xyz.quaver.pupil.types.Tag
import xyz.quaver.pupil.util.translations
import xyz.quaver.pupil.util.wordCapitalize
@@ -39,12 +40,6 @@ class TagChip(context: Context, _tag: Tag) : Chip(context) {
}
}
- private val languages = context.resources.getStringArray(R.array.languages).map {
- it.split("|").let { split ->
- Pair(split[0], split[1])
- }
- }.toMap()
-
init {
when(tag.area) {
"male" -> {
@@ -90,7 +85,8 @@ class TagChip(context: Context, _tag: Tag) : Chip(context) {
}
text = when (tag.area) {
- "language" -> languages[tag.tag]
+ // TODO languageMap
+ "language" -> Hitomi.languageMap[tag.tag]
else -> (translations[tag.tag] ?: tag.tag).wordCapitalize()
}
diff --git a/app/src/main/java/xyz/quaver/pupil/util/camera.kt b/app/src/main/java/xyz/quaver/pupil/util/camera.kt
deleted file mode 100644
index 1b2e5652..00000000
--- a/app/src/main/java/xyz/quaver/pupil/util/camera.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Pupil, Hitomi.la viewer for Android
- * Copyright (C) 2020 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 .
- */
-
-@file:Suppress("DEPRECATION", "Recycle")
-
-package xyz.quaver.pupil.util
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.graphics.ImageFormat
-import android.graphics.SurfaceTexture
-import android.hardware.Camera
-import android.view.Surface
-import android.view.WindowManager
-import com.google.android.gms.tasks.Task
-import com.google.mlkit.vision.common.InputImage
-import com.google.mlkit.vision.face.Face
-import com.google.mlkit.vision.face.FaceDetection
-import com.google.mlkit.vision.face.FaceDetectorOptions
-
-/** Check if this device has a camera */
-private fun Context.checkCameraHardware() =
- this.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
-
-private fun openFrontCamera() : Pair {
- var camera: Camera? = null
- var cameraID: Int = -1
-
- val cameraInfo = Camera.CameraInfo()
-
- for (i in 0 until Camera.getNumberOfCameras()) {
- Camera.getCameraInfo(i, cameraInfo)
- if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT)
- runCatching { Camera.open(i) }.getOrNull()?.let { camera = it; cameraID = i }
-
- if (camera != null) break
- }
-
- return Pair(camera, cameraID)
-}
-
-val orientations = mapOf(
- Surface.ROTATION_0 to 0,
- Surface.ROTATION_90 to 90,
- Surface.ROTATION_180 to 180,
- Surface.ROTATION_270 to 270,
-)
-
-private fun getRotation(context: Context, cameraID: Int): Int {
- val cameraRotation = Camera.CameraInfo().also { Camera.getCameraInfo(cameraID, it) }.orientation
- val rotation = orientations[(context.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation] ?: error("")
-
- return (cameraRotation + rotation) % 360
-}
-
-var camera: Camera? = null
-var surfaceTexture: SurfaceTexture? = null
-private val detector = FaceDetection.getClient(
- FaceDetectorOptions.Builder()
- .setClassificationMode(FaceDetectorOptions.CLASSIFICATION_MODE_ALL)
- .build()
-)
-private var process: Task>? = null
-
-fun startCamera(context: Context, callback: (List) -> Unit) {
- if (camera != null) closeCamera()
-
- val cameraID = openFrontCamera().let { (cam, cameraID) ->
- cam ?: return
- camera = cam
- cameraID
- }
-
- with (camera!!) {
- parameters = parameters.apply {
- setPreviewSize(640, 480)
- previewFormat = ImageFormat.NV21
- }
-
- setPreviewTexture(surfaceTexture ?: SurfaceTexture(0).also {
- surfaceTexture = it
- })
- startPreview()
- setPreviewCallback { bytes, _ ->
- if (process?.isComplete == false)
- return@setPreviewCallback
-
- val rotation = getRotation(context, cameraID)
-
- val image = InputImage.fromByteArray(bytes, 640, 480, rotation, InputImage.IMAGE_FORMAT_NV21)
- process = detector.process(image)
- .addOnSuccessListener(callback)
- }
- }
-}
-
-fun closeCamera() {
- camera?.setPreviewCallback(null)
- camera?.stopPreview()
- surfaceTexture?.release()
- surfaceTexture = null
- camera?.release()
- camera = null
-}
\ No newline at end of file
diff --git a/app/src/main/res/layout/progress_card_view.xml b/app/src/main/res/layout/progress_card_view.xml
index 671a789f..ed004cd2 100644
--- a/app/src/main/res/layout/progress_card_view.xml
+++ b/app/src/main/res/layout/progress_card_view.xml
@@ -65,7 +65,7 @@
android:id="@+id/progressbar"
android:layout_width="match_parent"
android:layout_height="4dp"
- android:progress="50"
+ android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
diff --git a/app/src/main/res/layout/reader_item.xml b/app/src/main/res/layout/reader_item.xml
index 0cee0987..ea7c44fb 100644
--- a/app/src/main/res/layout/reader_item.xml
+++ b/app/src/main/res/layout/reader_item.xml
@@ -63,7 +63,7 @@
-
@@ -70,30 +66,12 @@
app:layout_constraintTop_toBottomOf="@id/title" />
-
-
-
-
+ app:layout_constraintLeft_toRightOf="@id/thumbnail"/>
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index dfb2976b..85bb0c21 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -8,46 +8,6 @@
- 50
-
- - indonesian|Bahasa Indonesia
- - catalan|català
- - cebuano|Cebuano
- - czech|Čeština
- - danish|Dansk
- - german|Deutsch
- - estonian|eesti
- - english|English
- - spanish|Español
- - esperanto|Esperanto
- - french|Français
- - italian|Italiano
- - latin|Latina
- - hungarian|magyar
- - dutch|Nederlands
- - norwegian|norsk
- - polish|polski
- - portuguese|Português
- - romanian|română
- - albanian|shqip
- - slovak|Slovenčina
- - finnish|Suomi
- - swedish|Svenska
- - tagalog|Tagalog
- - vietnamese|tiếng việt
- - turkish|Türkçe
- - greek|Ελληνικά
- - mongolian|Монгол
- - russian|Русский
- - ukrainian|Українська
- - hebrew|עברית
- - arabic|العربية
- - persian|فارسی
- - thai|ไทย
- - korean|한국어
- - chinese|中文
- - japanese|日本語
-
-
- HITOMI|hitomi.la
- HIYOBI|hiyobi.me
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 90b50039..57b5cb5d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -100,7 +100,7 @@
Series: %1$s
Type: %1$s
Language: %1$s
- %dP
+ %sP
diff --git a/gradle.properties b/gradle.properties
index 2c2f1284..d53d94e5 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -19,5 +19,6 @@ org.gradle.caching=true
kotlin.code.style=official
android.enableJetifier=true
android.useAndroidX=true
+android.enableBuildCache=true
kotlin_version=1.4.20
\ No newline at end of file