From 052990c4ef9c3cc8229d28e6fabf840515e56c66 Mon Sep 17 00:00:00 2001 From: tom5079 Date: Thu, 16 Dec 2021 22:51:17 +0900 Subject: [PATCH] Added hiyobi.io --- .idea/misc.xml | 1 + app/build.gradle.kts | 8 + .../quaver/pupil/ExampleInstrumentedTest.kt | 9 + .../java/xyz/quaver/pupil/sources/Common.kt | 6 +- .../java/xyz/quaver/pupil/sources/History.kt | 3 - .../java/xyz/quaver/pupil/sources/Hitomi.kt | 30 +- .../xyz/quaver/pupil/sources/Hiyobi_io.kt | 441 ++++++++++++++++++ .../java/xyz/quaver/pupil/ui/BaseActivity.kt | 67 --- .../java/xyz/quaver/pupil/ui/LockActivity.kt | 280 ----------- .../java/xyz/quaver/pupil/ui/MainActivity.kt | 11 +- .../xyz/quaver/pupil/ui/ReaderActivity.kt | 6 +- .../xyz/quaver/pupil/ui/SettingsActivity.kt | 3 +- .../ui/dialog/DefaultQueryDialogFragment.kt | 2 +- .../pupil/ui/dialog/SourceSelectDialog.kt | 15 +- .../pupil/ui/fragment/LockSettingsFragment.kt | 146 ------ .../pupil/ui/fragment/SettingsFragment.kt | 17 - .../pupil/ui/view/FloatingSearchView.kt | 2 +- .../quaver/pupil/ui/view/ProgressCardView.kt | 15 - .../ui/viewmodel/GalleryDialogViewModel.kt | 59 --- .../pupil/ui/viewmodel/MainViewModel.kt | 6 +- .../pupil/ui/viewmodel/ReaderViewModel.kt | 61 ++- .../xyz/quaver/pupil/util/DownloadManager.kt | 1 - .../main/java/xyz/quaver/pupil/util/misc.kt | 9 - .../main/res/layout/search_result_item.xml | 38 -- .../java/xyz/quaver/pupil/ExampleUnitTest.kt | 6 + 25 files changed, 533 insertions(+), 709 deletions(-) create mode 100644 app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt delete mode 100644 app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt delete mode 100644 app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt delete mode 100644 app/src/main/java/xyz/quaver/pupil/ui/fragment/LockSettingsFragment.kt delete mode 100644 app/src/main/java/xyz/quaver/pupil/ui/viewmodel/GalleryDialogViewModel.kt delete mode 100644 app/src/main/res/layout/search_result_item.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 6a57076b..16390d4e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -41,6 +41,7 @@ + diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2ca85270..c70e60b6 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -58,6 +58,14 @@ android { jvmTarget = "1.8" freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } + packagingOptions { + resources.excludes.addAll( + listOf( + "META-INF/AL2.0", + "META-INF/LGPL2.1" + ) + ) + } } dependencies { diff --git a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt index 26ecdbf8..a43a9cf2 100644 --- a/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/xyz/quaver/pupil/ExampleInstrumentedTest.kt @@ -23,6 +23,15 @@ package xyz.quaver.pupil import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry +import com.google.api.Http +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.engine.okhttp.* +import io.ktor.client.request.* +import io.ktor.client.statement.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import org.junit.Test import org.junit.runner.RunWith 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 3656de96..f2ef7917 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -28,7 +28,6 @@ import org.kodein.di.* import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.pupil.R -import xyz.quaver.pupil.sources.hitomi.Hitomi interface ItemInfo : Parcelable { val source: String @@ -39,7 +38,7 @@ interface ItemInfo : Parcelable { @Parcelize class DefaultSearchSuggestion(override val body: String) : SearchSuggestion -data class SearchResultEvent(val type: Type, val payload: String) { +data class SearchResultEvent(val type: Type, val itemID: String, val payload: Parcelable? = null) { enum class Type { OPEN_READER, OPEN_DETAILS, @@ -75,7 +74,8 @@ val sourceModule = DI.Module(name = "source") { bindSet() listOf<(Application) -> (Source)>( - { Hitomi(it) } + { Hitomi(it) }, + { Hiyobi_io(it) } ).forEach { source -> inSet { singleton { source.invoke(instance()).let { it.name to it } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/History.kt b/app/src/main/java/xyz/quaver/pupil/sources/History.kt index fa4809b7..600c052a 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/History.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/History.kt @@ -26,11 +26,8 @@ import kotlinx.coroutines.launch import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.direct -import org.kodein.di.instance import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion -import xyz.quaver.pupil.db.AppDatabase import xyz.quaver.pupil.util.database -import xyz.quaver.pupil.util.source class History(override val di: DI) : Source(), DIAware { diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt b/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt index 0f48060d..ef4d75f0 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package xyz.quaver.pupil.sources.hitomi +package xyz.quaver.pupil.sources import android.app.Application import android.view.LayoutInflater @@ -40,12 +40,9 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.colorResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.LiveData -import androidx.room.* import coil.annotation.ExperimentalCoilApi import coil.compose.rememberImagePainter import com.google.accompanist.flowlayout.FlowRow @@ -57,7 +54,6 @@ import kotlinx.coroutines.sync.withLock import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable -import org.kodein.di.DI import org.kodein.di.DIAware import org.kodein.di.android.closestDI import org.kodein.di.instance @@ -69,9 +65,9 @@ import xyz.quaver.hitomi.* import xyz.quaver.pupil.R import xyz.quaver.pupil.db.AppDatabase import xyz.quaver.pupil.db.Bookmark -import xyz.quaver.pupil.sources.ItemInfo -import xyz.quaver.pupil.sources.SearchResultEvent -import xyz.quaver.pupil.sources.Source +import xyz.quaver.pupil.ui.theme.Blue700 +import xyz.quaver.pupil.ui.theme.Orange500 +import xyz.quaver.pupil.ui.theme.Pink600 import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.wordCapitalize import kotlin.math.max @@ -152,7 +148,7 @@ class Hitomi(app: Application) : Source(), DIAware { var cachedSortMode: Int = -1 private val cache = mutableListOf() - override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> = coroutineScope { withContext(Dispatchers.IO) { + override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> = withContext(Dispatchers.IO) { if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) { cachedQuery = null cache.clear() @@ -179,8 +175,8 @@ class Hitomi(app: Application) : Source(), DIAware { channel.close() } - Pair(channel, cache.size) - } } + channel to cache.size + } override suspend fun suggestion(query: String) : List { return getSuggestionsForQuery(query.takeLastWhile { !it.isWhitespace() }).map { @@ -336,10 +332,10 @@ class Hitomi(app: Application) : Source(), DIAware { } val (surfaceColor, textTint) = when { - isFavorite -> Pair(colorResource(id = R.color.material_orange_500), Color.White) + isFavorite -> Pair(Orange500, Color.White) else -> when (tagParts[0]) { - "male" -> Pair(colorResource(id = R.color.material_blue_700), Color.White) - "female" -> Pair(colorResource(id = R.color.material_pink_600), Color.White) + "male" -> Pair(Blue700, Color.White) + "female" -> Pair(Pink600, Color.White) else -> Pair(MaterialTheme.colors.background, MaterialTheme.colors.onBackground) } } @@ -394,7 +390,7 @@ class Hitomi(app: Application) : Source(), DIAware { var isFolded by remember { mutableStateOf(true) } val bookmarkedTags by bookmarkDao.getAll(name).observeAsState(emptyList()) - val bookmarkedTagsInList = bookmarkedTags.toSet() intersect tags + val bookmarkedTagsInList = bookmarkedTags.toSet() intersect tags.toSet() FlowRow(Modifier.padding(0.dp, 16.dp)) { tags.sortedBy { if (bookmarkedTagsInList.contains(it)) 0 else 1 }.let { (if (isFolded) it.take(10) else it) }.forEach { tag -> @@ -454,7 +450,9 @@ class Hitomi(app: Application) : Source(), DIAware { val painter = rememberImagePainter(itemInfo.thumbnail) - Column { + Column( + modifier = Modifier.clickable { onEvent(SearchResultEvent(SearchResultEvent.Type.OPEN_READER, itemInfo.itemID, itemInfo)) } + ) { Row { Image( painter = painter, diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt b/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt new file mode 100644 index 00000000..83b59797 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/Hiyobi_io.kt @@ -0,0 +1,441 @@ +/* + * Pupil, Hitomi.la viewer for Android + * Copyright (C) 2021 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 + +import android.app.Application +import android.os.Parcelable +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Female +import androidx.compose.material.icons.filled.Male +import androidx.compose.material.icons.filled.Star +import androidx.compose.material.icons.outlined.StarOutline +import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import coil.annotation.ExperimentalCoilApi +import coil.compose.rememberImagePainter +import com.google.accompanist.flowlayout.FlowRow +import io.ktor.client.* +import io.ktor.client.request.* +import io.ktor.http.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.Channel +import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.int +import kotlinx.serialization.json.jsonArray +import kotlinx.serialization.json.jsonPrimitive +import org.kodein.di.DIAware +import org.kodein.di.android.closestDI +import org.kodein.di.instance +import org.kodein.log.LoggerFactory +import org.kodein.log.newLogger +import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion +import xyz.quaver.pupil.R +import xyz.quaver.pupil.db.AppDatabase +import xyz.quaver.pupil.db.Bookmark +import xyz.quaver.pupil.ui.theme.Blue700 +import xyz.quaver.pupil.ui.theme.Orange500 +import xyz.quaver.pupil.ui.theme.Pink600 +import xyz.quaver.pupil.util.content +import xyz.quaver.pupil.util.get +import xyz.quaver.pupil.util.wordCapitalize + +@Serializable +@Parcelize +data class Tag( + val male: Int?, + val female: Int?, + val tag: String +) : Parcelable { + override fun toString(): String { + val stringBuilder = StringBuilder() + + stringBuilder.append(when { + male != null -> "male" + female != null -> "female" + else -> "tag" + }) + stringBuilder.append(':') + stringBuilder.append(tag) + + return stringBuilder.toString() + } +} + +@Serializable +@Parcelize +data class HiyobiItemInfo( + override val itemID: String, + override val title: String, + val thumbnail: String, + val artists: List, + val series: List, + val type: String, + val date: String, + val bookmark: Unit?, + val tags: List, + val commentCount: Int, + val pageCount: Int +): ItemInfo { + override val source: String + get() = "hiyobi.io" +} + +@Serializable +data class Manga( + val mangaId: Int, + val title: String, + val artist: List, + val thumbnail: String, + val series: List, + val type: String, + val date: String, + val bookmark: Unit?, + val tags: List, + val commentCount: Int, + val pageCount: Int +) + +@Serializable +data class QueryManga( + val nowPage: Int, + val maxPage: Int, + val manga: List +) + +@Serializable +data class SearchResultData( + val queryManga: QueryManga +) + +@Serializable +data class SearchResult( + val data: SearchResultData +) + +class Hiyobi_io(app: Application): Source(), DIAware { + override val di by closestDI(app) + + private val logger = newLogger(LoggerFactory.default) + + private val database: AppDatabase by instance() + private val bookmarkDao = database.bookmarkDao() + + override val name = "hiyobi.io" + override val iconResID = R.drawable.hitomi + override val preferenceID = 0 + override val availableSortMode = emptyList() + + private val client: HttpClient by instance() + + private suspend fun query(page: Int, tags: String): SearchResult { + val query = "{queryManga(page:$page,tags:$tags){nowPage maxPage manga{mangaId title artist thumbnail series type date bookmark tags{male female tag} commentCount pageCount}}}" + + return client.get("https://api.hiyobi.io/api?query=$query") + } + + private suspend fun totalCount(tags: String): Int { + val firstPageQuery = "{queryManga(page:1,tags:$tags){maxPage}}" + val maxPage = client.get( + "https://api.hiyobi.io/api?query=$firstPageQuery" + )["data"]!!["queryManga"]!!["maxPage"]!!.jsonPrimitive.int + + val lastPageQuery = "{queryManga(page:$maxPage,tags:$tags){manga{mangaId}}}" + val lastPageCount = client.get( + "https://api.hiyobi.io/api?query=$lastPageQuery" + )["data"]!!["queryManga"]!!["manga"]!!.jsonArray.size + + return (maxPage-1)*25+lastPageCount + } + + override suspend fun search( + query: String, + range: IntRange, + sortMode: Int + ): Pair, Int> = withContext(Dispatchers.IO) { + val channel = Channel() + + val tags = parseQuery(query) + + logger.info { + tags + } + + CoroutineScope(Dispatchers.IO).launch { + (range.first/25+1 .. range.last/25+1).map { page -> + page to async { query(page, tags) } + }.forEach { (page, result) -> + result.await().data.queryManga.manga.forEachIndexed { index, manga -> + if ((page-1)*25+index in range) channel.send(transform(manga)) + } + } + + channel.close() + } + + channel to totalCount(tags) + } + + override suspend fun suggestion(query: String): List { + return emptyList() + } + + override suspend fun images(itemID: String): List = withContext(Dispatchers.IO) { + val query = "{getManga(mangaId:$itemID){urls}}" + + client.post("https://api.hiyobi.io/api") { + contentType(ContentType.Application.Json) + body = mapOf("query" to query) + }["data"]!!["getManga"]!!["urls"]!!.jsonArray.map { "https://api.hiyobi.io/${it.content!!}" } + } + + override suspend fun info(itemID: String): ItemInfo { + TODO("Not yet implemented") + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun TagChip(tag: Tag, isFavorite: Boolean, onClick: ((Tag) -> Unit)? = null, onFavoriteClick: ((Tag) -> Unit)? = null) { + val icon = when { + tag.male != null -> Icons.Filled.Male + tag.female != null -> Icons.Filled.Female + else -> null + } + + val (surfaceColor, textTint) = when { + isFavorite -> Pair(Orange500, Color.White) + else -> when { + tag.male != null -> Pair(Blue700, Color.White) + tag.female != null -> Pair(Pink600, Color.White) + else -> Pair(MaterialTheme.colors.background, MaterialTheme.colors.onBackground) + } + } + + val starIcon = if (isFavorite) Icons.Filled.Star else Icons.Outlined.StarOutline + + Surface( + modifier = Modifier.padding(2.dp), + onClick = { onClick?.invoke(tag) }, + shape = RoundedCornerShape(16.dp), + color = surfaceColor, + elevation = 2.dp + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + if (icon != null) + Icon( + icon, + contentDescription = "Icon", + modifier = Modifier + .padding(4.dp) + .size(24.dp), + tint = Color.White + ) + else + Box(Modifier.size(16.dp)) + + Text( + tag.tag, + color = textTint, + style = MaterialTheme.typography.body2 + ) + + Icon( + starIcon, + contentDescription = "Favorites", + modifier = Modifier + .padding(8.dp) + .size(16.dp) + .clip(CircleShape) + .clickable { onFavoriteClick?.invoke(tag) }, + tint = textTint + ) + } + } + } + + @OptIn(ExperimentalMaterialApi::class) + @Composable + fun TagGroup(tags: List) { + var isFolded by remember { mutableStateOf(true) } + val bookmarkedTags by bookmarkDao.getAll(name).observeAsState(emptyList()) + + val bookmarkedTagsInList = tags.filter { it.toString() in bookmarkedTags } + + FlowRow(Modifier.padding(0.dp, 16.dp)) { + tags.sortedBy { if (bookmarkedTagsInList.contains(it)) 0 else 1 }.let { (if (isFolded) it.take(10) else it) }.forEach { tag -> + TagChip( + tag = tag, + isFavorite = bookmarkedTagsInList.contains(tag), + onFavoriteClick = { + val bookmarkTag = Bookmark(name, it.toString()) + + CoroutineScope(Dispatchers.IO).launch { + if (bookmarkedTagsInList.contains(it)) + bookmarkDao.delete(bookmarkTag) + else + bookmarkDao.insert(bookmarkTag) + } + } + ) + } + + if (isFolded && tags.size > 10) + Surface( + modifier = Modifier.padding(2.dp), + color = MaterialTheme.colors.background, + shape = RoundedCornerShape(16.dp), + elevation = 2.dp, + onClick = { isFolded = false } + ) { + Text( + "…", + modifier = Modifier.padding(16.dp, 8.dp), + color = MaterialTheme.colors.onBackground, + style = MaterialTheme.typography.body2 + ) + } + } + } + + @OptIn(ExperimentalCoilApi::class) + @Composable + override fun SearchResult(itemInfo: ItemInfo, onEvent: (SearchResultEvent) -> Unit) { + itemInfo as HiyobiItemInfo + + val painter = rememberImagePainter(itemInfo.thumbnail) + + Row( + modifier = Modifier.clickable { + onEvent(SearchResultEvent(SearchResultEvent.Type.OPEN_READER, itemInfo.itemID, itemInfo)) + } + ) { + Image( + painter = painter, + contentDescription = null, + modifier = Modifier + .requiredWidth(150.dp) + .aspectRatio( + with(painter.intrinsicSize) { if (this == Size.Unspecified) 1f else width / height }, + true + ) + .padding(0.dp, 0.dp, 8.dp, 0.dp) + .align(Alignment.CenterVertically), + contentScale = ContentScale.FillWidth + ) + + Column { + Text( + itemInfo.title, + style = MaterialTheme.typography.h6, + color = MaterialTheme.colors.onSurface + ) + + val artistStringBuilder = StringBuilder() + + with (itemInfo.artists) { + if (this.isNotEmpty()) + artistStringBuilder.append(this.joinToString(", ") { it.wordCapitalize() }) + } + + if (artistStringBuilder.isNotEmpty()) + Text( + artistStringBuilder.toString(), + style = MaterialTheme.typography.subtitle1, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + ) + + if (itemInfo.series.isNotEmpty()) + Text( + stringResource( + id = R.string.galleryblock_series, + itemInfo.series.joinToString { it.wordCapitalize() } + ), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + ) + + Text( + stringResource(id = R.string.galleryblock_type, itemInfo.type), + style = MaterialTheme.typography.body2, + color = MaterialTheme.colors.onSurface.copy(alpha = 0.6F) + ) + + key(itemInfo.tags) { + TagGroup(tags = itemInfo.tags) + } + } + } + } + + companion object { + private fun transform(manga: Manga) = HiyobiItemInfo( + manga.mangaId.toString(), + manga.title, + "https://api.hiyobi.io/${manga.thumbnail}", + manga.artist, + manga.series, + manga.type, + manga.date, + manga.bookmark, + manga.tags, + manga.commentCount, + manga.pageCount + ) + + fun parseQuery(query: String): String { + val queryBuilder = StringBuilder("[") + + if (query.isNotBlank()) + query.split(' ').filter { it.isNotBlank() }.forEach { + val tags = it.replace('_', ' ').split(':', limit = 2) + + if (queryBuilder.length != 1) queryBuilder.append(',') + + queryBuilder.append( + when { + tags.size == 1 -> "{tag:\"${tags[0]}\"}" + tags[0] == "male" -> "{male:1,tag:\"${tags[1]}\"}" + tags[0] == "female" -> "{female:1,tag:\"${tags[1]}\"}" + else -> "{tag:\"${tags[1]}\"}" + } + ) + } + + return queryBuilder.append(']').toString() + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt deleted file mode 100644 index 96d8b3b3..00000000 --- a/app/src/main/java/xyz/quaver/pupil/ui/BaseActivity.kt +++ /dev/null @@ -1,67 +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 . - */ - -package xyz.quaver.pupil.ui - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.os.PersistableBundle -import android.view.WindowManager -import androidx.activity.result.contract.ActivityResultContracts -import androidx.annotation.CallSuper -import androidx.appcompat.app.AppCompatActivity -import xyz.quaver.pupil.R -import xyz.quaver.pupil.util.LockManager -import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.normalizeID - -open class BaseActivity : AppCompatActivity() { - - private var locked: Boolean = true - - private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) - locked = false - else - finish() - } - - @CallSuper - override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) { - super.onCreate(savedInstanceState, persistentState) - - locked = !LockManager(this).locks.isNullOrEmpty() - } - - @CallSuper - override fun onResume() { - super.onResume() - - if (Preferences["security_mode"]) - window.setFlags( - WindowManager.LayoutParams.FLAG_SECURE, - WindowManager.LayoutParams.FLAG_SECURE) - else - window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) - - if (locked) - lockLauncher.launch(Intent(this, LockActivity::class.java)) - } - -} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt deleted file mode 100644 index 33e678f6..00000000 --- a/app/src/main/java/xyz/quaver/pupil/ui/LockActivity.kt +++ /dev/null @@ -1,280 +0,0 @@ -/* - * 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 . - */ - -package xyz.quaver.pupil.ui - -import android.app.Activity -import android.app.AlertDialog -import android.os.Bundle -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import androidx.appcompat.app.AppCompatActivity -import androidx.biometric.BiometricManager -import androidx.biometric.BiometricPrompt -import androidx.core.content.ContextCompat -import com.andrognito.patternlockview.PatternLockView -import com.google.android.material.snackbar.Snackbar -import xyz.quaver.pupil.R -import xyz.quaver.pupil.databinding.LockActivityBinding -import xyz.quaver.pupil.ui.fragment.PINLockFragment -import xyz.quaver.pupil.ui.fragment.PatternLockFragment -import xyz.quaver.pupil.util.Lock -import xyz.quaver.pupil.util.LockManager -import xyz.quaver.pupil.util.Preferences - -private var lastUnlocked = 0L -class LockActivity : AppCompatActivity() { - - private lateinit var lockManager: LockManager - private var mode: String? = null - - private lateinit var binding: LockActivityBinding - - private val patternLockFragment = PatternLockFragment().apply { - var lastPass = "" - onPatternDrawn = { - when(mode) { - null -> { - val result = lockManager.check(it) - - if (result == true) { - lastUnlocked = System.currentTimeMillis() - setResult(Activity.RESULT_OK) - finish() - } else - binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) - } - "add_lock" -> { - if (lastPass.isEmpty()) { - lastPass = it - - Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show() - } else { - if (lastPass == it) { - LockManager(context!!).add(Lock.generate(Lock.Type.PATTERN, it)) - finish() - } else { - binding.patternLockView.setViewMode(PatternLockView.PatternViewMode.WRONG) - lastPass = "" - - Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show() - } - } - } - } - } - } - - private val pinLockFragment = PINLockFragment().apply { - var lastPass = "" - onPINEntered = { - when(mode) { - null -> { - val result = lockManager.check(it) - - if (result == true) { - lastUnlocked = System.currentTimeMillis() - setResult(Activity.RESULT_OK) - finish() - } else { - binding.indicatorDots.startAnimation(AnimationUtils.loadAnimation(context, R.anim.shake).apply { - setAnimationListener(object: Animation.AnimationListener { - override fun onAnimationEnd(animation: Animation?) { - binding.pinLockView.resetPinLockView() - binding.pinLockView.isEnabled = true - } - - override fun onAnimationStart(animation: Animation?) { - binding.pinLockView.isEnabled = false - } - - override fun onAnimationRepeat(animation: Animation?) { - // Do Nothing - } - }) - }) - } - } - "add_lock" -> { - if (lastPass.isEmpty()) { - lastPass = it - - binding.pinLockView.resetPinLockView() - Snackbar.make(view!!, R.string.settings_lock_confirm, Snackbar.LENGTH_LONG).show() - } else { - if (lastPass == it) { - LockManager(context!!).add(Lock.generate(Lock.Type.PIN, it)) - finish() - } else { - binding.indicatorDots.startAnimation(AnimationUtils.loadAnimation(context, R.anim.shake).apply { - setAnimationListener(object: Animation.AnimationListener { - override fun onAnimationEnd(animation: Animation?) { - binding.pinLockView.resetPinLockView() - binding.pinLockView.isEnabled = true - } - - override fun onAnimationStart(animation: Animation?) { - binding.pinLockView.isEnabled = false - } - - override fun onAnimationRepeat(animation: Animation?) { - // Do Nothing - } - }) - }) - lastPass = "" - - Snackbar.make(view!!, R.string.settings_lock_wrong_confirm, Snackbar.LENGTH_LONG).show() - } - } - } - } - } - } - - private fun showBiometricPrompt() { - val promptInfo = BiometricPrompt.PromptInfo.Builder() - .setTitle(getText(R.string.settings_lock_fingerprint_prompt)) - .setSubtitle(getText(R.string.settings_lock_fingerprint_prompt_subtitle)) - .setNegativeButtonText(getText(android.R.string.cancel)) - .setConfirmationRequired(false) - .build() - - val biometricPrompt = BiometricPrompt(this, ContextCompat.getMainExecutor(this), - object : BiometricPrompt.AuthenticationCallback() { - override fun onAuthenticationSucceeded( - result: BiometricPrompt.AuthenticationResult) { - super.onAuthenticationSucceeded(result) - lastUnlocked = System.currentTimeMillis() - setResult(RESULT_OK) - finish() - return - } - }) - - // Displays the "log in" prompt. - biometricPrompt.authenticate(promptInfo) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = LockActivityBinding.inflate(layoutInflater) - setContentView(binding.root) - - lockManager = try { - LockManager(this) - } catch (e: Exception) { - AlertDialog.Builder(this).apply { - setTitle(R.string.warning) - setMessage(R.string.lock_corrupted) - setPositiveButton(android.R.string.ok) { _, _ -> - finish() - } - }.show() - return - } - - mode = intent.getStringExtra("mode") - val force = intent.getBooleanExtra("force", false) - - when(mode) { - null -> { - if (lockManager.isEmpty()) { - setResult(RESULT_OK) - finish() - return - } - - if (System.currentTimeMillis() - lastUnlocked < 5*60*1000 && !force) { - lastUnlocked = System.currentTimeMillis() - setResult(RESULT_OK) - finish() - return - } - - if ( - Preferences["lock_fingerprint"] - && BiometricManager.from(this).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS - ) { - binding.fingerprintBtn.apply { - isEnabled = true - setOnClickListener { - showBiometricPrompt() - } - } - showBiometricPrompt() - } - - binding.patternBtn.apply { - isEnabled = lockManager.contains(Lock.Type.PATTERN) - setOnClickListener { - supportFragmentManager.beginTransaction().replace( - R.id.lock_content, patternLockFragment - ).commit() - } - } - binding.pinBtn.apply { - isEnabled = lockManager.contains(Lock.Type.PIN) - setOnClickListener { - supportFragmentManager.beginTransaction().replace( - R.id.lock_content, pinLockFragment - ).commit() - } - } - binding.passwordBtn.isEnabled = false - - when (lockManager.locks!!.first().type) { - Lock.Type.PIN -> { - - supportFragmentManager.beginTransaction().add( - R.id.lock_content, pinLockFragment - ).commit() - } - Lock.Type.PATTERN -> { - supportFragmentManager.beginTransaction().add( - R.id.lock_content, patternLockFragment - ).commit() - } - else -> return - } - } - "add_lock" -> { - binding.patternBtn.isEnabled = false - binding.pinBtn.isEnabled = false - binding.fingerprintBtn.isEnabled = false - binding.passwordBtn.isEnabled = false - - when(intent.getStringExtra("type")!!) { - "pattern" -> { - binding.patternBtn.isEnabled = true - supportFragmentManager.beginTransaction().add( - R.id.lock_content, patternLockFragment - ).commit() - } - "pin" -> { - binding.pinBtn.isEnabled = true - supportFragmentManager.beginTransaction().add( - R.id.lock_content, pinLockFragment - ).commit() - } - } - } - } - } - -} 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 86201752..b2b09f77 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -53,7 +53,6 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter import kotlinx.coroutines.* import org.kodein.di.DIAware import org.kodein.di.android.closestDI -import org.kodein.di.compose.withDI import org.kodein.log.LoggerFactory import org.kodein.log.newLogger import xyz.quaver.pupil.* @@ -65,7 +64,6 @@ import xyz.quaver.pupil.ui.composable.FloatingSearchBar import xyz.quaver.pupil.ui.composable.MultipleFloatingActionButton import xyz.quaver.pupil.ui.composable.SubFabItem import xyz.quaver.pupil.ui.dialog.SourceSelectDialog -import xyz.quaver.pupil.ui.dialog.SourceSelectDialogItem import xyz.quaver.pupil.ui.theme.PupilTheme import xyz.quaver.pupil.ui.view.ProgressCardView import xyz.quaver.pupil.ui.viewmodel.MainViewModel @@ -117,8 +115,12 @@ class MainActivity : ComponentActivity(), DIAware { } if (openSourceSelectDialog) - SourceSelectDialog { + SourceSelectDialog( + currentSource = model.source.name, + onDismissRequest = { openSourceSelectDialog = false } + ) { source -> openSourceSelectDialog = false + model.setSourceAndReset(source.name) } Scaffold( @@ -185,7 +187,8 @@ class MainActivity : ComponentActivity(), DIAware { ReaderActivity::class.java ).apply { putExtra("source", model.source.name) - putExtra("id", itemInfo.itemID) + putExtra("id", event.itemID) + putExtra("payload", event.payload) }) } else -> TODO("") 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 89c5ea65..56ce631d 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/ReaderActivity.kt @@ -81,8 +81,6 @@ class ReaderActivity : ComponentActivity(), DIAware { setContent { var isFABExpanded by remember { mutableStateOf(FloatingActionButtonState.COLLAPSED) } val isFullscreen by model.isFullscreen.observeAsState(false) - val title by model.title.observeAsState(stringResource(R.string.reader_loading)) - val sourceIcon by model.sourceIcon.observeAsState() val imageSources = remember { mutableStateListOf() } val imageHeights = remember { mutableStateListOf() } val states = remember { mutableStateListOf() } @@ -131,12 +129,12 @@ class ReaderActivity : ComponentActivity(), DIAware { TopAppBar( title = { Text( - title, + model.title ?: stringResource(R.string.reader_loading), color = MaterialTheme.colors.onSecondary ) }, actions = { - sourceIcon?.let { sourceIcon -> + model.sourceIcon?.let { sourceIcon -> Image( modifier = Modifier.size(36.dp), painter = painterResource(id = sourceIcon), diff --git a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt index 2bf54e78..90b0f0e5 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/SettingsActivity.kt @@ -20,11 +20,12 @@ package xyz.quaver.pupil.ui import android.os.Bundle import android.view.MenuItem +import androidx.appcompat.app.AppCompatActivity import xyz.quaver.pupil.R import xyz.quaver.pupil.ui.fragment.SettingsFragment import xyz.quaver.pupil.ui.fragment.SourceSettingsFragment -class SettingsActivity : BaseActivity() { +class SettingsActivity : AppCompatActivity() { companion object { const val SETTINGS_EXTRA = "xyz.quaver.pupil.ui.SettingsActivity.SETTINGS_EXTRA" diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt index 0fc1e23f..bc389958 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/DefaultQueryDialogFragment.kt @@ -27,7 +27,7 @@ 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.sources.Hitomi import xyz.quaver.pupil.types.Tags import xyz.quaver.pupil.util.Preferences diff --git a/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt b/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt index c51296d5..4e68bf32 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt @@ -19,6 +19,7 @@ package xyz.quaver.pupil.ui.dialog import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -36,7 +37,7 @@ import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.sources.SourceEntries @Composable -fun SourceSelectDialogItem(source: Source) { +fun SourceSelectDialogItem(source: Source, isSelected: Boolean, onSelected: (Source) -> Unit = { }) { Row( modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically, @@ -59,16 +60,20 @@ fun SourceSelectDialogItem(source: Source) { tint = MaterialTheme.colors.onSurface.copy(alpha = 0.5f) ) - Button(onClick = { /*TODO*/ }) { + Button( + enabled = !isSelected, + onClick = { + onSelected(source) + } + ) { Text("GO") } } } -@Preview @Composable -fun SourceSelectDialog(onDismissRequest: () -> Unit = { }) { +fun SourceSelectDialog(currentSource: String, onDismissRequest: () -> Unit = { }, onSelected: (Source) -> Unit = { }) { val sourceEntries: SourceEntries by rememberInstance() Dialog(onDismissRequest = onDismissRequest) { @@ -77,7 +82,7 @@ fun SourceSelectDialog(onDismissRequest: () -> Unit = { }) { shape = RoundedCornerShape(12.dp) ) { Column() { - sourceEntries.forEach { SourceSelectDialogItem(it.second) } + sourceEntries.forEach { SourceSelectDialogItem(it.second, it.first == currentSource, onSelected) } } } } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/fragment/LockSettingsFragment.kt b/app/src/main/java/xyz/quaver/pupil/ui/fragment/LockSettingsFragment.kt deleted file mode 100644 index 06a319cb..00000000 --- a/app/src/main/java/xyz/quaver/pupil/ui/fragment/LockSettingsFragment.kt +++ /dev/null @@ -1,146 +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 . - */ - -package xyz.quaver.pupil.ui.fragment - -import android.content.Intent -import android.os.Bundle -import android.widget.Toast -import androidx.appcompat.app.AlertDialog -import androidx.preference.Preference -import androidx.preference.PreferenceFragmentCompat -import androidx.preference.SwitchPreferenceCompat -import xyz.quaver.pupil.R -import xyz.quaver.pupil.ui.LockActivity -import xyz.quaver.pupil.util.Lock -import xyz.quaver.pupil.util.LockManager -import xyz.quaver.pupil.util.Preferences - -class LockSettingsFragment : PreferenceFragmentCompat() { - - override fun onResume() { - super.onResume() - - val lockManager = LockManager(requireContext()) - - findPreference("lock_pattern")?.summary = - if (lockManager.contains(Lock.Type.PATTERN)) - getString(R.string.settings_lock_enabled) - else - "" - - findPreference("lock_pin")?.summary = - if (lockManager.contains(Lock.Type.PIN)) - getString(R.string.settings_lock_enabled) - else - "" - - if (lockManager.isEmpty()) { - (findPreference("lock_fingerprint") as SwitchPreferenceCompat).isChecked = false - - Preferences["lock_fingerprint"] = false - } - } - - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { - setPreferencesFromResource(R.xml.lock_preferences, rootKey) - - with (findPreference("lock_pattern")) { - this!! - - if (LockManager(requireContext()).contains(Lock.Type.PATTERN)) - summary = getString(R.string.settings_lock_enabled) - - onPreferenceClickListener = Preference.OnPreferenceClickListener { - val lockManager = LockManager(requireContext()) - - if (lockManager.contains(Lock.Type.PATTERN)) { - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.warning) - setMessage(R.string.settings_lock_remove_message) - - setPositiveButton(android.R.string.ok) { _, _ -> - lockManager.remove(Lock.Type.PATTERN) - onResume() - } - setNegativeButton(android.R.string.cancel) { _, _ -> } - }.show() - } else { - val intent = Intent(requireContext(), LockActivity::class.java).apply { - putExtra("mode", "add_lock") - putExtra("type", "pattern") - } - - startActivity(intent) - } - - true - } - } - - with (findPreference("lock_pin")) { - this!! - - if (LockManager(requireContext()).contains(Lock.Type.PIN)) - summary = getString(R.string.settings_lock_enabled) - - onPreferenceClickListener = Preference.OnPreferenceClickListener { - val lockManager = LockManager(requireContext()) - - if (lockManager.contains(Lock.Type.PIN)) { - AlertDialog.Builder(requireContext()).apply { - setTitle(R.string.warning) - setMessage(R.string.settings_lock_remove_message) - - setPositiveButton(android.R.string.ok) { _, _ -> - lockManager.remove(Lock.Type.PIN) - onResume() - } - setNegativeButton(android.R.string.cancel) { _, _ -> } - }.show() - } else { - val intent = Intent(requireContext(), LockActivity::class.java).apply { - putExtra("mode", "add_lock") - putExtra("type", "pin") - } - - startActivity(intent) - } - - true - } - } - - with (findPreference("lock_fingerprint")) { - this!! - - setOnPreferenceChangeListener { _, newValue -> - this as SwitchPreferenceCompat - - if (newValue == true && LockManager(requireContext()).isEmpty()) { - isChecked = false - - Toast.makeText(requireContext(), R.string.settings_lock_fingerprint_without_lock, Toast.LENGTH_SHORT).show() - } else - isChecked = newValue as Boolean - - false - } - } - } -} \ No newline at end of file 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 c3f0a408..78c9ed77 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 @@ -32,7 +32,6 @@ import org.kodein.di.instance import xyz.quaver.io.FileX import xyz.quaver.io.util.getChild import xyz.quaver.pupil.R -import xyz.quaver.pupil.ui.LockActivity import xyz.quaver.pupil.ui.SettingsActivity import xyz.quaver.pupil.ui.dialog.* import xyz.quaver.pupil.util.* @@ -49,16 +48,6 @@ class SettingsFragment : private val downloadManager: DownloadManager by instance() - private val lockLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (it.resultCode == Activity.RESULT_OK) { - parentFragmentManager - .beginTransaction() - .replace(R.id.settings, LockSettingsFragment()) - .addToBackStack("Lock") - .commitAllowingStateLoss() - } - } - override fun onResume() { super.onResume() @@ -88,12 +77,6 @@ class SettingsFragment : "download_folder" -> { DownloadLocationDialogFragment().show(parentFragmentManager, "Download Location Dialog") } - "app_lock" -> { - val intent = Intent(requireContext(), LockActivity::class.java).apply { - putExtra("force", true) - } - lockLauncher.launch(intent) - } "proxy" -> { ProxyDialogFragment().show(parentFragmentManager, "Proxy Dialog") } diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt index 62df1506..2125545d 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/FloatingSearchView.kt @@ -33,7 +33,7 @@ import xyz.quaver.floatingsearchview.databinding.SearchSuggestionItemBinding import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.pupil.R -import xyz.quaver.pupil.sources.hitomi.Hitomi +import xyz.quaver.pupil.sources.Hitomi import xyz.quaver.pupil.types.FavoriteHistorySwitch import xyz.quaver.pupil.types.HistorySuggestion import xyz.quaver.pupil.types.LoadingSuggestion diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt index a5ff55b9..4c3527a6 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/ProgressCardView.kt @@ -1,27 +1,12 @@ package xyz.quaver.pupil.ui.view -import android.content.Context -import android.util.AttributeSet -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.cardview.widget.CardView import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.DrawableCompat -import xyz.quaver.pupil.R -import xyz.quaver.pupil.databinding.ProgressCardViewBinding -import xyz.quaver.pupil.sources.ItemInfo -import xyz.quaver.pupil.sources.Source @OptIn(ExperimentalFoundationApi::class) @Composable diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/GalleryDialogViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/GalleryDialogViewModel.kt deleted file mode 100644 index 9e179272..00000000 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/GalleryDialogViewModel.kt +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Pupil, Hitomi.la viewer for Android - * Copyright (C) 2021 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.ui.viewmodel - -import android.app.Application -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.* -import org.kodein.di.DIAware -import org.kodein.di.android.x.closestDI -import xyz.quaver.pupil.sources.ItemInfo -import xyz.quaver.pupil.sources.Source -import xyz.quaver.pupil.util.source - -class GalleryDialogViewModel(app: Application) : AndroidViewModel(app), DIAware { - - override val di by closestDI() - - private val _info = MutableLiveData() - val info: LiveData = _info - - private val _related = MutableLiveData>() - val related: LiveData> = _related - - fun load(source: String, itemID: String) {/* - val source: Source by source(source) - - viewModelScope.launch { - _info.value = withContext(Dispatchers.IO) { - source.info(itemID) - }.also { - _related.value = it.extra[ItemInfo.ExtraType.RELATED_ITEM]?.await()?.split(", ")?.map { related -> - async(Dispatchers.IO) { - source.info(related) - } - }?.awaitAll() - } - }*/ - } - -} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt index a5546030..c9781298 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/MainViewModel.kt @@ -31,7 +31,6 @@ import org.kodein.log.LoggerFactory import org.kodein.log.newLogger import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion import xyz.quaver.pupil.sources.* -import xyz.quaver.pupil.sources.hitomi.Hitomi import xyz.quaver.pupil.util.Preferences import xyz.quaver.pupil.util.source import kotlin.math.ceil @@ -59,7 +58,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { direct.source(it) } private var sourceFactory: (String) -> Source = defaultSourceFactory - var source by mutableStateOf(sourceFactory("hitomi.la")) + var source by mutableStateOf(sourceFactory("hiyobi.io")) private set var sortModeIndex by mutableStateOf(0) @@ -125,13 +124,12 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { sortModeIndex ) - logger.info { count.toString() } - totalItems.postValue(count) for (result in channel) { yield() searchResults.add(result) + logger.info { result.toString() } } loading = false diff --git a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt index e80c1135..a3880e37 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/viewmodel/ReaderViewModel.kt @@ -22,10 +22,7 @@ package xyz.quaver.pupil.ui.viewmodel import android.app.Application import android.content.Intent import android.net.Uri -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue +import androidx.compose.runtime.* import androidx.lifecycle.* import io.ktor.client.request.* import io.ktor.http.* @@ -36,11 +33,13 @@ import org.kodein.di.DIAware import org.kodein.di.android.x.closestDI import org.kodein.di.direct import org.kodein.di.instance +import org.kodein.di.instanceOrNull import org.kodein.log.LoggerFactory import org.kodein.log.newLogger import xyz.quaver.pupil.db.AppDatabase import xyz.quaver.pupil.db.Bookmark import xyz.quaver.pupil.db.History +import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.Source import xyz.quaver.pupil.util.NetworkCache import xyz.quaver.pupil.util.source @@ -61,14 +60,12 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { private val historyDao = database.historyDao() private val bookmarkDao = database.bookmarkDao() - private val _source = MutableLiveData() - val source = _source as LiveData - - private val _itemID = MutableLiveData() - val itemID = _itemID as LiveData - - private val _title = MutableLiveData() - val title = _title as LiveData + var source by mutableStateOf(null) + private set + var itemID by mutableStateOf(null) + private set + var title by mutableStateOf(null) + private set private val totalProgressMutex = Mutex() var totalProgress by mutableStateOf(0) @@ -80,19 +77,8 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { val imageList = mutableStateListOf() val progressList = mutableStateListOf() - val isBookmarked = Transformations.switchMap(MediatorLiveData>().apply { - addSource(source) { source -> itemID.value?.let { itemID -> source to itemID } } - addSource(itemID) { itemID -> source.value?.let { source -> source to itemID } } - }) { (source, itemID) -> - bookmarkDao.contains(source.name, itemID) - } - - val sourceInstance = Transformations.map(source) { - direct.source(it) - } - - val sourceIcon = Transformations.map(sourceInstance) { - it.iconResID + val sourceIcon by derivedStateOf { + source?.iconResID } /** @@ -105,8 +91,8 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { val uri = intent.data val lastPathSegment = uri?.lastPathSegment if (uri != null && lastPathSegment != null) { - _source.value = uri.host ?: error("Source cannot be null") - _itemID.value = when (uri.host) { + source = uri.host?.let { direct.source(it) } ?: error("Invalid host") + itemID = when (uri.host) { "hitomi.la" -> Regex("([0-9]+).html").find(lastPathSegment)?.groupValues?.get(1) ?: error("Invalid itemID") "hiyobi.me" -> lastPathSegment @@ -115,15 +101,16 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { } } } else { - _source.value = intent.getStringExtra("source") ?: error("Invalid source") - _itemID.value = intent.getStringExtra("id") ?: error("Invalid itemID") + source = intent.getStringExtra("source")?.let { direct.source(it) } ?: error("Invalid source") + itemID = intent.getStringExtra("id") ?: error("Invalid itemID") + title = intent.getParcelableExtra("payload")?.title } } @OptIn(ExperimentalCoroutinesApi::class) fun load() { - val source: Source by source(source.value ?: return) - val itemID = itemID.value ?: return + val source = source ?: return + val itemID = itemID ?: return viewModelScope.launch { launch(Dispatchers.IO) { @@ -132,9 +119,10 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { } viewModelScope.launch { - _title.value = withContext(Dispatchers.IO) { - source.info(itemID) - }.title + if (title == null) + title = withContext(Dispatchers.IO) { + source.info(itemID) + }.title } viewModelScope.launch { @@ -152,6 +140,9 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { } images.forEachIndexed { index, image -> + logger.info { + progressList.toList().toString() + } when (val scheme = image.takeWhile { it != ':' }) { "http", "https" -> { val (channel, file) = cache.load { @@ -203,7 +194,7 @@ class ReaderViewModel(app: Application) : AndroidViewModel(app), DIAware { } fun toggleBookmark() { - val bookmark = source.value?.let { source -> itemID.value?.let { itemID -> Bookmark(source, itemID) } } ?: return + val bookmark = source?.let { source -> itemID?.let { itemID -> Bookmark(source.name, itemID) } } ?: return CoroutineScope(Dispatchers.IO).launch { if (bookmarkDao.contains(bookmark).value ?: return@launch) diff --git a/app/src/main/java/xyz/quaver/pupil/util/DownloadManager.kt b/app/src/main/java/xyz/quaver/pupil/util/DownloadManager.kt index 333042ed..6ba525d2 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/DownloadManager.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/DownloadManager.kt @@ -31,7 +31,6 @@ import org.kodein.di.DIAware import org.kodein.di.android.closestDI import xyz.quaver.io.FileX import xyz.quaver.io.util.* -import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.Source class DownloadManager constructor(context: Context) : ContextWrapper(context), DIAware { diff --git a/app/src/main/java/xyz/quaver/pupil/util/misc.kt b/app/src/main/java/xyz/quaver/pupil/util/misc.kt index 72c8dd07..0cb01414 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -19,24 +19,16 @@ package xyz.quaver.pupil.util import android.annotation.SuppressLint -import android.content.Context -import android.content.res.Resources import android.graphics.BitmapFactory -import android.graphics.BitmapRegionDecoder import android.view.MenuItem import android.view.View import androidx.compose.runtime.* -import androidx.compose.ui.Modifier -import androidx.compose.ui.composed -import androidx.compose.ui.focus.onFocusEvent import androidx.compose.ui.geometry.Rect import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.toAndroidRect -import androidx.compose.ui.platform.LocalFocusManager import androidx.lifecycle.MutableLiveData -import com.google.accompanist.insets.LocalWindowInsets import kotlinx.serialization.json.* import org.kodein.di.DIAware import org.kodein.di.DirectDIAware @@ -49,7 +41,6 @@ import xyz.quaver.io.util.inputStream import xyz.quaver.pupil.db.AppDatabase import xyz.quaver.pupil.sources.ItemInfo import xyz.quaver.pupil.sources.SourceEntries -import java.io.IOException import java.io.InputStream import java.io.OutputStream import java.util.* diff --git a/app/src/main/res/layout/search_result_item.xml b/app/src/main/res/layout/search_result_item.xml deleted file mode 100644 index 79efbb85..00000000 --- a/app/src/main/res/layout/search_result_item.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt index 652d776a..9e0c0674 100644 --- a/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt +++ b/app/src/test/java/xyz/quaver/pupil/ExampleUnitTest.kt @@ -33,6 +33,7 @@ import kotlinx.serialization.json.Json import org.junit.Test import xyz.quaver.hitomi.getGalleryInfo import xyz.quaver.hitomi.imageUrlFromImage +import xyz.quaver.pupil.sources.Hiyobi_io import java.lang.reflect.ParameterizedType import kotlin.reflect.KClass import kotlin.reflect.KType @@ -50,4 +51,9 @@ class ExampleUnitTest { } } + @Test + fun test2() { + print(Hiyobi_io.parseQuery("female:loli female:big_breast tag:group")) + } + }