diff --git a/app/build.gradle b/app/build.gradle index 9b296214..4c84cc84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -124,7 +124,7 @@ dependencies { implementation "xyz.quaver:libpupil:1.9.7" implementation "xyz.quaver:documentfilex:0.4-alpha02" - implementation "xyz.quaver:floatingsearchview:1.0.7" + implementation "xyz.quaver:floatingsearchview:1.0.9" testImplementation "junit:junit:4.13.1" androidTestImplementation "androidx.test.ext:junit:1.1.2" diff --git a/app/src/main/java/xyz/quaver/pupil/Pupil.kt b/app/src/main/java/xyz/quaver/pupil/Pupil.kt index 75776794..0a6d2b4d 100644 --- a/app/src/main/java/xyz/quaver/pupil/Pupil.kt +++ b/app/src/main/java/xyz/quaver/pupil/Pupil.kt @@ -42,6 +42,7 @@ import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import xyz.quaver.io.FileX +import xyz.quaver.pupil.sources.initSources import xyz.quaver.pupil.types.Tag import xyz.quaver.pupil.util.* import xyz.quaver.setClient @@ -89,6 +90,8 @@ class Pupil : Application() { firebaseAnalytics = Firebase.analytics FirebaseCrashlytics.getInstance().setUserId(userID) + initSources(this) + val proxyInfo = getProxyInfo() clientBuilder = OkHttpClient.Builder() diff --git a/app/src/main/java/xyz/quaver/pupil/adapters/SourceAdapter.kt b/app/src/main/java/xyz/quaver/pupil/adapters/SourceAdapter.kt new file mode 100644 index 00000000..3010eb4d --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/adapters/SourceAdapter.kt @@ -0,0 +1,59 @@ +/* + * 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.adapters + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.graphics.drawable.DrawableCompat +import androidx.recyclerview.widget.RecyclerView +import xyz.quaver.pupil.databinding.SourceSelectDialogItemBinding +import xyz.quaver.pupil.sources.Source +import xyz.quaver.pupil.sources.sourceIcons + +class SourceAdapter(private val sources: List>) : RecyclerView.Adapter() { + + var onSourceSelectedListener: ((Source<*>) -> Unit)? = null + + inner class ViewHolder(private val binding: SourceSelectDialogItemBinding) : RecyclerView.ViewHolder(binding.root) { + lateinit var source: Source<*> + + init { + binding.go.setOnClickListener { + onSourceSelectedListener?.invoke(source) + } + } + + fun bind(source: Source<*>) { + this.source = source + + binding.icon.setImageDrawable(sourceIcons[source.name]) + binding.name.text = source.name + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder(SourceSelectDialogItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(sources[position]) + } + + override fun getItemCount(): Int = sources.size +} \ No newline at end of file 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 e1a5f0fb..cbe3c8ab 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/Common.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/Common.kt @@ -18,9 +18,13 @@ package xyz.quaver.pupil.sources +import android.content.Context +import android.graphics.drawable.Drawable +import androidx.core.content.ContextCompat import kotlinx.coroutines.channels.Channel import xyz.quaver.pupil.R -import kotlin.reflect.KClass +import xyz.quaver.pupil.sources.hitomi.Hitomi +import xyz.quaver.pupil.sources.hitomi.Hiyobi data class SearchResult( val id: String, @@ -49,8 +53,28 @@ data class SearchResult( } } -interface Source> { - val querySortModeClass: KClass? +enum class DefaultSortMode { + DEFAULT +} +interface Source> { + val name: String + val iconResID: Int + val availableSortMode: Array - suspend fun query(query: String, range: IntRange, sortMode: Query_SortMode? = null) : Pair, Int> + suspend fun query(query: String, range: IntRange, sortMode: Enum<*>) : Pair, Int> +} + +val sources = mutableMapOf>() +val sourceIcons = mutableMapOf() + +@Suppress("UNCHECKED_CAST") +fun initSources(context: Context) { + // Add Default Sources + listOf( + Hitomi(), + Hiyobi() + ).forEach { + sources[it.name] = it + sourceIcons[it.name] = ContextCompat.getDrawable(context, it.iconResID) + } } \ 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 98d9b097..d73fbb8e 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 @@ -21,6 +21,7 @@ package xyz.quaver.pupil.sources.hitomi import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import xyz.quaver.hitomi.* +import xyz.quaver.pupil.R import xyz.quaver.pupil.sources.SearchResult import xyz.quaver.pupil.sources.SearchResult.ExtraType import xyz.quaver.pupil.sources.Source @@ -30,18 +31,20 @@ import kotlin.math.min class Hitomi : Source { - override val querySortModeClass = SortMode::class - enum class SortMode { NEWEST, POPULAR } + override val name: String = "hitomi.la" + override val iconResID: Int = R.drawable.hitomi + override val availableSortMode: Array = SortMode.values() + var cachedQuery: String? = null var cachedSortMode: SortMode? = null val cache = mutableListOf() - override suspend fun query(query: String, range: IntRange, sortMode: SortMode?): Pair, Int> { + override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>): Pair, Int> { if (cachedQuery != query || cachedSortMode != sortMode || cache.isEmpty()) { cachedQuery = null cache.clear() 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 index cd62be61..8da782de 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hiyobi.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/hitomi/Hiyobi.kt @@ -23,14 +23,19 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.launch import xyz.quaver.hiyobi.* +import xyz.quaver.pupil.R +import xyz.quaver.pupil.sources.DefaultSortMode 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 +class Hiyobi : Source { - override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>?): Pair, Int> { + override val name: String = "hiyobi.me" + override val iconResID: Int = R.drawable.ic_hiyobi + override val availableSortMode: Array = DefaultSortMode.values() + + override suspend fun query(query: String, range: IntRange, sortMode: Enum<*>): Pair, Int> { val channel = Channel() val (results, total) = if (query.isEmpty()) @@ -59,7 +64,7 @@ class Hiyobi : Source> { 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.TYPE to { galleryBlock.type.name.replace('_', ' ').wordCapitalize() }, SearchResult.ExtraType.PAGECOUNT to { getGalleryInfo(galleryBlock.id).files.size.toString() } ), galleryBlock.tags.map { it.value } 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 2b04d51c..dcc885df 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt +++ b/app/src/main/java/xyz/quaver/pupil/ui/MainActivity.kt @@ -25,23 +25,27 @@ import android.os.Bundle import android.text.InputType import android.text.util.Linkify import android.view.KeyEvent +import android.view.Menu import android.view.MenuItem import android.view.View import android.view.animation.DecelerateInterpolator import android.widget.EditText +import android.widget.ImageView import android.widget.TextView import androidx.appcompat.app.AlertDialog -import androidx.cardview.widget.CardView import androidx.core.view.GravityCompat import androidx.core.view.ViewCompat +import androidx.core.view.children +import androidx.core.widget.ImageViewCompat 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 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 import xyz.quaver.floatingsearchview.util.view.SearchInputView import xyz.quaver.hitomi.getSuggestionsForQuery import xyz.quaver.pupil.* @@ -50,18 +54,16 @@ import xyz.quaver.pupil.databinding.MainActivityBinding 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.sources.sourceIcons +import xyz.quaver.pupil.sources.sources import xyz.quaver.pupil.types.* import xyz.quaver.pupil.ui.dialog.DownloadLocationDialogFragment import xyz.quaver.pupil.ui.dialog.GalleryDialog +import xyz.quaver.pupil.ui.dialog.SourceSelectDialog import xyz.quaver.pupil.ui.view.ProgressCardView import xyz.quaver.pupil.ui.view.SwipePageTurnView -import xyz.quaver.pupil.util.ItemClickSupport -import xyz.quaver.pupil.util.Preferences -import xyz.quaver.pupil.util.checkUpdate +import xyz.quaver.pupil.util.* 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 @@ -82,9 +84,8 @@ class MainActivity : } private var queryStack = mutableListOf() - @Suppress("UNCHECKED_CAST") - private var source: Source> = Hiyobi() as Source> - private var sortMode = Hitomi.SortMode.NEWEST + private lateinit var source: Source<*> + private lateinit var sortMode: Enum<*> private var searchJob: Deferred, Int>>? = null private var totalItems = 0 @@ -97,6 +98,8 @@ class MainActivity : binding = MainActivityBinding.inflate(layoutInflater) setContentView(binding.root) + setSource(sources.values.first()) + if (intent.action == Intent.ACTION_VIEW) { intent.dataString?.let { url -> restore(url, @@ -167,6 +170,34 @@ class MainActivity : } } + private fun setSource(source: Source<*>) { + this.source = source + sortMode = source.availableSortMode.first() + + query = "" + currentPage = 1 + + with (binding.contents.searchview.binding.querySection.menuView) { + post { + menuItems.findMenu(R.id.sort).subMenu.apply { + clear() + + source.availableSortMode.forEach { + add(R.id.sort_mode_group_id, it.ordinal, Menu.NONE, it.name) + } + + setGroupCheckable(R.id.sort_mode_group_id, true, true) + + children.first().isChecked = true + } + with (getChildAt(1) as ImageView) { + ImageViewCompat.setImageTintList(this, null) + setImageDrawable(sourceIcons[source.name]) + } + } + } + } + private fun initView() { binding.contents.recyclerview.addOnScrollListener(object: RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { @@ -175,7 +206,7 @@ class MainActivity : min( max( binding.contents.searchview.translationY - dy, - -binding.contents.searchview.findViewById(R.id.search_query_section).height.toFloat() + -binding.contents.searchview.binding.querySection.root.height.toFloat() ), 0F) if (dy > 0) @@ -224,6 +255,7 @@ class MainActivity : with(binding.contents.randomFab) { setImageResource(R.drawable.shuffle_variant) setOnClickListener { + setImageDrawable(CircularProgressDrawable(context)) if (totalItems > 0) CoroutineScope(Dispatchers.IO).launch { val random = Random.Default.nextInt(totalItems) @@ -236,6 +268,7 @@ class MainActivity : ).first.receive() launch(Dispatchers.Main) { + setImageResource(R.drawable.shuffle_variant) GalleryDialog(this@MainActivity, randomResult.id).apply { onChipClickedHandler.add { query = it.toQuery() @@ -394,7 +427,7 @@ class MainActivity : with(binding.contents.searchview) { onMenuStatusChangeListener = object: FloatingSearchView.OnMenuStatusChangeListener { override fun onMenuOpened() { - (binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems() + (this@MainActivity.binding.contents.recyclerview.adapter as SearchResultsAdapter).closeAllItems() } override fun onMenuClosed() { @@ -402,14 +435,6 @@ class MainActivity : } } - post { - findViewById(R.id.menu_view).menuItems.firstOrNull { - (it as MenuItem).itemId == R.id.main_menu_thin - }?.let { - (it as MenuItem).isChecked = Preferences["thin"] - } - } - onHistoryDeleteClickedListener = { searchHistory.remove(it) swapSuggestions(defaultSuggestions) @@ -473,37 +498,32 @@ class MainActivity : } } - attachNavigationDrawerToMenuButton(binding.drawer) + attachNavigationDrawerToMenuButton(this@MainActivity.binding.drawer) } } - fun onActionMenuItemSelected(item: MenuItem?) { - when(item?.itemId) { - R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) - R.id.main_menu_thin -> { - // TODO + private fun onActionMenuItemSelected(item: MenuItem?) { + if (item?.groupId == R.id.sort_mode_group_id) { + currentPage = 1 + sortMode = source.availableSortMode.let { availableSortMode -> + availableSortMode.getOrElse(item.itemId) { availableSortMode.first() } } - R.id.main_menu_sort_newest -> { - sortMode = Hitomi.SortMode.NEWEST - item.isChecked = true - runOnUiThread { - currentPage = 1 - - query() - } - } - R.id.main_menu_sort_popular -> { - sortMode = Hitomi.SortMode.POPULAR - item.isChecked = true - - runOnUiThread { - currentPage = 1 - - query() - } - } + query() } + else + when(item?.itemId) { + R.id.main_menu_settings -> startActivity(Intent(this@MainActivity, SettingsActivity::class.java)) + R.id.source -> SourceSelectDialog().apply { + onSourceSelectedListener = { + setSource(it) + + query() + + dismiss() + } + }.show(supportFragmentManager, null) + } } override fun onNavigationItemSelected(item: MenuItem): Boolean { @@ -539,6 +559,7 @@ class MainActivity : private fun clearGalleries() = CoroutineScope(Dispatchers.Main).launch { searchResults.clear() + (binding.contents.recyclerview.adapter as RecyclerSwipeAdapter).mItemManger.closeAllItems() binding.contents.recyclerview.adapter?.notifyDataSetChanged() binding.contents.noresult.visibility = View.INVISIBLE 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 c4249c85..f30dff14 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 @@ -64,12 +64,7 @@ class GalleryDialog(context: Context, private val galleryID: String) : AlertDial binding = GalleryDialogBinding.inflate(layoutInflater) setContentView(binding.root) - window?.attributes.apply { - this ?: return@apply - - width = LayoutParams.MATCH_PARENT - height = LayoutParams.MATCH_PARENT - } + window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) with(binding.fab) { setImageDrawable(ContextCompat.getDrawable(context, R.drawable.arrow_right)) 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 new file mode 100644 index 00000000..1ca45b79 --- /dev/null +++ b/app/src/main/java/xyz/quaver/pupil/ui/dialog/SourceSelectDialog.kt @@ -0,0 +1,50 @@ +/* + * 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.dialog + +import android.app.Dialog +import android.os.Bundle +import android.view.ViewGroup.LayoutParams +import android.view.Window +import androidx.fragment.app.DialogFragment +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import xyz.quaver.pupil.adapters.SourceAdapter +import xyz.quaver.pupil.sources.Source +import xyz.quaver.pupil.sources.sources + +class SourceSelectDialog : DialogFragment() { + + var onSourceSelectedListener: ((Source<*>) -> Unit)? = null + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return Dialog(requireContext()).apply { + window?.requestFeature(Window.FEATURE_NO_TITLE) + window?.setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) + + setContentView(RecyclerView(context).apply { + layoutManager = LinearLayoutManager(context) + adapter = SourceAdapter(sources.values.toList()).apply { + onSourceSelectedListener = this@SourceSelectDialog.onSourceSelectedListener + } + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java b/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java index fe42c007..da5065cf 100644 --- a/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java +++ b/app/src/main/java/xyz/quaver/pupil/ui/view/SwipePageTurnView.java @@ -407,6 +407,16 @@ public class SwipePageTurnView extends ViewGroup implements NestedScrollingChild stopNestedScroll(); } + @Override + public boolean onNestedPreFling(View target, float velocityX, float velocityY) { + return dispatchNestedPreFling(velocityX, velocityY); + } + + @Override + public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { + return dispatchNestedFling(velocityX, velocityY, consumed); + } + // NestedScrollingChild @Override 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 c4272cff..32531c34 100644 --- a/app/src/main/java/xyz/quaver/pupil/util/misc.kt +++ b/app/src/main/java/xyz/quaver/pupil/util/misc.kt @@ -22,6 +22,7 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.os.Build +import android.view.MenuItem import androidx.core.content.ContextCompat import kotlinx.serialization.json.* import okhttp3.OkHttpClient @@ -142,4 +143,8 @@ operator fun JsonElement.get(tag: String) = this.jsonObject[tag] val JsonElement.content - get() = this.jsonPrimitive.contentOrNull \ No newline at end of file + get() = this.jsonPrimitive.contentOrNull + +fun List.findMenu(itemID: Int): MenuItem { + return first { it.itemId == itemID } +} \ No newline at end of file diff --git a/app/src/main/res/layout/source_select_dialog_item.xml b/app/src/main/res/layout/source_select_dialog_item.xml new file mode 100644 index 00000000..82c25d2f --- /dev/null +++ b/app/src/main/res/layout/source_select_dialog_item.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + +