diff --git a/app/build.gradle b/app/build.gradle index fa929846..e3839680 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -74,14 +74,14 @@ dependencies { implementation "io.ktor:ktor-client-serialization:1.6.1" implementation "androidx.appcompat:appcompat:1.3.0" - implementation "androidx.activity:activity-ktx:1.3.0-rc01" - implementation "androidx.fragment:fragment-ktx:1.3.5" + implementation "androidx.activity:activity-ktx:1.3.0-rc02" + implementation "androidx.fragment:fragment-ktx:1.3.6" implementation "androidx.preference:preference-ktx:1.1.1" implementation "androidx.recyclerview:recyclerview:1.2.1" implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.gridlayout:gridlayout:1.0.0" implementation "androidx.biometric:biometric:1.1.0" - implementation "androidx.work:work-runtime-ktx:2.6.0-beta01" + implementation "androidx.work:work-runtime-ktx:2.6.0-beta02" implementation 'org.kodein.di:kodein-di-framework-android-x:7.6.0' @@ -104,7 +104,7 @@ dependencies { implementation 'com.github.piasy:FrescoImageLoader:1.8.0' implementation 'com.github.piasy:FrescoImageViewFactory:1.8.0' - implementation "org.jsoup:jsoup:1.13.1" + implementation "org.jsoup:jsoup:1.14.1" implementation "com.tbuonomo:dotsindicator:4.2" @@ -120,7 +120,7 @@ dependencies { debugImplementation "com.orhanobut:logger:2.2.0" debugImplementation "com.squareup.leakcanary:leakcanary-android:2.6" - testImplementation "junit:junit:4.13.1" + testImplementation "junit:junit:4.13.2" testImplementation "org.mockito:mockito-inline:3.11.2" androidTestImplementation "androidx.test.ext:junit:1.1.3" 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 85ac3113..290e6906 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -107,25 +107,21 @@ data class ItemInfo( } } -enum class DefaultSortMode : SortModeInterface { - DEFAULT -} - @Parcelize class DefaultSearchSuggestion(override val body: String) : SearchSuggestion interface SortModeInterface { val ordinal: Int - val name: String + val name: Int } abstract class Source { abstract val name: String abstract val iconResID: Int abstract val preferenceID: Int - abstract val availableSortMode: List + abstract val availableSortMode: List - abstract suspend fun search(query: String, range: IntRange, sortMode: SortModeInterface) : Pair, Int> + abstract suspend fun search(query: String, range: IntRange, sortMode: Int) : Pair, Int> abstract suspend fun suggestion(query: String) : List abstract suspend fun images(itemID: String) : List abstract suspend fun info(itemID: String) : ItemInfo @@ -146,11 +142,13 @@ val sourceModule = DI.Module(name = "source") { bindSet() bindSet() - listOf( - Hitomi() - ).forEach { source -> - inSet { multiton { _: Unit -> source.name to source } } - inSet { singleton { source.name to source.preferenceID } } + onReady { + listOf( + Hitomi(instance()) + ).forEach { source -> + inSet { multiton { _: Unit -> source.name to source } } + inSet { singleton { source.name to source.preferenceID } } + } } bind { factory { source: String -> History(di, source) } } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/Downloads.kt b/app/src/main/java/xyz/quaver/pupil/sources/Downloads.kt index aff9e9d1..a889c10a 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Downloads.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Downloads.kt @@ -41,11 +41,11 @@ class Downloads(override val di: DI) : Source(), DIAware { get() = R.drawable.ic_download override val preferenceID: Int get() = R.xml.download_preferences - override val availableSortMode: List = DefaultSortMode.values().toList() + override val availableSortMode: List = emptyList() private val downloadManager: DownloadManager by instance() - override suspend fun search(query: String, range: IntRange, sortMode: SortModeInterface): Pair, Int> { + override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> { val downloads = downloadManager.downloads.toList() val channel = Channel() 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 2500ab1f..8231321e 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/History.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/History.kt @@ -40,9 +40,9 @@ class History(override val di: DI, source: String) : Source(), DIAware { get() = source.iconResID override val preferenceID: Int get() = source.preferenceID - override val availableSortMode: List = DefaultSortMode.values().toList() + override val availableSortMode: List = emptyList() - override suspend fun search(query: String, range: IntRange, sortMode: SortModeInterface): Pair, Int> { + override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> { val channel = Channel() CoroutineScope(Dispatchers.IO).launch { 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 bc265fc3..4a7bcc2f 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Hitomi.kt @@ -18,6 +18,7 @@ package xyz.quaver.pupil.sources +import android.app.Application import android.view.LayoutInflater import android.widget.TextView import io.ktor.http.* @@ -35,12 +36,7 @@ import xyz.quaver.pupil.util.wordCapitalize import kotlin.math.max import kotlin.math.min -class Hitomi : Source() { - - enum class SortMode : SortModeInterface { - NEWEST, - POPULAR - } +class Hitomi(app: Application) : Source() { @Parcelize data class TagSuggestion(val s: String, val t: Int, val u: String, val n: String) : SearchSuggestion { @@ -60,18 +56,18 @@ class Hitomi : Source() { override val name: String = "hitomi.la" override val iconResID: Int = R.drawable.hitomi override val preferenceID: Int = R.xml.hitomi_preferences - override val availableSortMode: List = SortMode.values().toList() + override val availableSortMode: List = app.resources.getStringArray(R.array.hitomi_sort_mode).toList() var cachedQuery: String? = null - var cachedSortMode: SortMode? = null + var cachedSortMode: Int = -1 val cache = mutableListOf() - override suspend fun search(query: String, range: IntRange, sortMode: SortModeInterface): Pair, Int> { + override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> { if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) { cachedQuery = null cache.clear() yield() - doSearch("$query ${Preferences["hitomi.default_query", ""]}", sortMode == SortMode.POPULAR).let { + doSearch("$query ${Preferences["hitomi.default_query", ""]}", sortMode == 1).let { yield() cache.addAll(it) } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/ImHentai.kt b/app/src/main/java/xyz/quaver/pupil/sources/ImHentai.kt new file mode 100644 index 00000000..188a6fc8 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/sources/ImHentai.kt @@ -0,0 +1,88 @@ +/* + * 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 io.ktor.client.* +import io.ktor.client.features.* +import io.ktor.client.features.get +import io.ktor.client.request.* +import io.ktor.client.statement.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jsoup.Jsoup +import org.jsoup.nodes.Element +import org.kodein.di.DI +import org.kodein.di.DIAware +import org.kodein.di.instance +import xyz.quaver.floatingsearchview.suggestions.model.SearchSuggestion +import xyz.quaver.pupil.R + +class ImHentai(override val di: DI) : Source(), DIAware { + + private val app: Application by instance() + private val client: HttpClient by instance() + + override val name: String + get() = ImHentai.name + override val iconResID: Int + get() = R.drawable.ic_imhentai + override val preferenceID: Int + get() = R.xml.imhentai_preferences + override val availableSortMode = app.resources.getStringArray(R.array.imhentai_sort_mode).toList() + + override suspend fun search(query: String, range: IntRange, sortMode: Int): Pair, Int> = withContext(Dispatchers.IO) { + val channel = Channel() + + val doc = Jsoup.connect("https://imhentai.xxx/search/?key=$query").get() + + val count = countRegex.find(doc.getElementsByClass("heading2").text())?.groupValues?.get(1)?.toIntOrNull() ?: 0 + + launch { + doc.getElementsByClass("thumb") + } + + return@withContext Pair(channel, count) + } + + override suspend fun suggestion(query: String): List { + TODO("Not yet implemented") + } + + override suspend fun images(itemID: String): List { + TODO("Not yet implemented") + } + + override suspend fun info(itemID: String): ItemInfo { + TODO("Not yet implemented") + } + + companion object { + private const val name = "imhentai" + private val countRegex = Regex("""\(\d+\) results found.""") + private val idRegex = Regex("""/gallery/(\d+)/""") + + private fun transform(item: Element) { + val caption = item.select(".caption a") + } + } + +} \ 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 6cf44c3b..6b07c11a 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -41,7 +41,6 @@ import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.CircularProgressDrawable import com.daimajia.swipe.adapters.RecyclerSwipeAdapter import com.google.android.material.navigation.NavigationView -import com.google.android.material.snackbar.Snackbar import com.orhanobut.logger.Logger import kotlinx.coroutines.* import org.kodein.di.DIAware @@ -96,8 +95,18 @@ class MainActivity : binding.contents.searchview.binding.querySection.menuView.menuItems.findMenu(R.id.sort).subMenu.apply { clear() - it.forEach { - add(R.id.sort_mode_group_id, it.ordinal, Menu.NONE, it.name) + it.forEachIndexed { index, sortMode -> + add(R.id.sort_mode_group_id, index, Menu.NONE, sortMode).setOnMenuItemClickListener { + model.setPage(1) + model.sortModeIndex.value = it.itemId + + children.forEachIndexed { menuIndex, menuItem -> + menuItem.isChecked = menuIndex == index + } + + model.query() + true + } } setGroupCheckable(R.id.sort_mode_group_id, true, true) @@ -417,32 +426,23 @@ class MainActivity : } private fun onActionMenuItemSelected(item: MenuItem?) { - if (item?.groupId == R.id.sort_mode_group_id) { - model.setPage(1) - model.sortMode.value = model.availableSortMode.value?.let { availableSortMode -> - availableSortMode.getOrElse(item.itemId) { availableSortMode.first() } - } + when(item?.itemId) { + R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) + R.id.source -> SourceSelectDialog().apply { + onSourceSelectedListener = { + model.setSourceAndReset(it) - model.query() + dismiss() + } + + onSourceSettingsSelectedListener = { + startActivity(Intent(this@MainActivity, SettingsActivity::class.java).putExtra(SettingsActivity.SETTINGS_EXTRA, it)) + + refreshOnResume = true + dismiss() + } + }.show(supportFragmentManager, null) } - else - when(item?.itemId) { - R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) - R.id.source -> SourceSelectDialog().apply { - onSourceSelectedListener = { - model.setSourceAndReset(it) - - dismiss() - } - - onSourceSettingsSelectedListener = { - startActivity(Intent(this@MainActivity, SettingsActivity::class.java).putExtra(SettingsActivity.SETTINGS_EXTRA, it)) - - refreshOnResume = true - dismiss() - } - }.show(supportFragmentManager, null) - } } override fun onNavigationItemSelected(item: MenuItem): Boolean { 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 a68a7eb2..f3c7647d 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 @@ -60,7 +60,8 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { val availableSortMode = Transformations.map(_source) { it.availableSortMode } - val sortMode = MutableLiveData() + + val sortModeIndex = MutableLiveData() val sourceIcon = Transformations.map(_source) { it.iconResID @@ -86,7 +87,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { fun setSourceAndReset(sourceName: String) { _source.value = sourceFactory(sourceName).also { - sortMode.value = it.availableSortMode.first() + sortModeIndex.value = 0 } setQueryAndSearch() @@ -119,7 +120,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { fun query() { val perPage = Preferences["per_page", "25"].toInt() val source = _source.value ?: error("Source is null") - val sortMode = sortMode.value ?: source.availableSortMode.first() + val sortModeIndex = sortModeIndex.value ?: 0 val currentPage = currentPage.value ?: 1 suggestionJob?.cancel() @@ -134,7 +135,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { val (channel, count) = source.search( query.value ?: "", (currentPage - 1) * perPage until currentPage * perPage, - sortMode + sortModeIndex ) totalItems.postValue(count) @@ -168,7 +169,7 @@ class MainViewModel(app: Application) : AndroidViewModel(app), DIAware { _source.value?.search( query.value + Preferences["default_query", ""], random .. random, - sortMode.value!! + sortModeIndex.value!! )?.first?.receive() }?.let(callback) } diff --git a/app/src/main/res/drawable/ic_hiyobi.png b/app/src/main/res/drawable/ic_hiyobi.png deleted file mode 100644 index 2af60c05..00000000 Binary files a/app/src/main/res/drawable/ic_hiyobi.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_imhentai.png b/app/src/main/res/drawable/ic_imhentai.png new file mode 100644 index 00000000..191eb1d5 Binary files /dev/null and b/app/src/main/res/drawable/ic_imhentai.png differ diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 1e2fd1df..e446095d 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -19,4 +19,16 @@ SOCKS + + NEWEST + POPULAR + + + + LATEST + DOWNLOADED + POPULAR + TOP RATED + + \ No newline at end of file diff --git a/app/src/main/res/xml/imhentai_preferences.xml b/app/src/main/res/xml/imhentai_preferences.xml new file mode 100644 index 00000000..6f4b1e38 --- /dev/null +++ b/app/src/main/res/xml/imhentai_preferences.xml @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file