Add support for the predictive back gesture

pull/307/head
Koitharu 3 years ago
parent 652351f79a
commit f436a49e5f
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -24,6 +24,7 @@
android:allowBackup="true" android:allowBackup="true"
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent" android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
android:dataExtractionRules="@xml/backup_rules" android:dataExtractionRules="@xml/backup_rules"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="@xml/backup_content" android:fullBackupContent="@xml/backup_content"
android:fullBackupOnly="true" android:fullBackupOnly="true"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

@ -5,6 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -39,6 +40,8 @@ abstract class AlertDialogFragment<B : ViewBinding> : DialogFragment() {
open fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder = builder open fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder = builder
open fun onDialogCreated(dialog: AlertDialog) = Unit
protected fun bindingOrNull(): B? = viewBinding protected fun bindingOrNull(): B? = viewBinding
protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B protected abstract fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): B

@ -82,6 +82,7 @@ abstract class BaseActivity<B : ViewBinding> :
} }
override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) { override fun onOptionsItemSelected(item: MenuItem) = if (item.itemId == android.R.id.home) {
@Suppress("DEPRECATION")
onBackPressed() onBackPressed()
true true
} else super.onOptionsItemSelected(item) } else super.onOptionsItemSelected(item)
@ -130,7 +131,8 @@ abstract class BaseActivity<B : ViewBinding> :
window.statusBarColor = getThemeColor(android.R.attr.statusBarColor) window.statusBarColor = getThemeColor(android.R.attr.statusBarColor)
} }
@Suppress("OVERRIDE_DEPRECATION", "DEPRECATION") @Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
@Deprecated("Should not be used")
override fun onBackPressed() { override fun onBackPressed() {
if ( // https://issuetracker.google.com/issues/139738913 if ( // https://issuetracker.google.com/issues/139738913
Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && Build.VERSION.SDK_INT == Build.VERSION_CODES.Q &&

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams
import androidx.activity.OnBackPressedDispatcher
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
@ -30,6 +31,9 @@ abstract class BaseBottomSheet<B : ViewBinding> : BottomSheetDialogFragment() {
val isExpanded: Boolean val isExpanded: Boolean
get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED get() = behavior?.state == BottomSheetBehavior.STATE_EXPANDED
val onBackPressedDispatcher: OnBackPressedDispatcher
get() = (requireDialog() as AppBottomSheetDialog).onBackPressedDispatcher
final override fun onCreateView( final override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,

@ -0,0 +1,24 @@
package org.koitharu.kotatsu.base.ui.util
import android.view.MenuItem
import android.view.MenuItem.OnActionExpandListener
import androidx.activity.OnBackPressedCallback
class CollapseActionViewCallback(
private val menuItem: MenuItem
) : OnBackPressedCallback(menuItem.isActionViewExpanded), OnActionExpandListener {
override fun handleOnBackPressed() {
menuItem.collapseActionView()
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean {
isEnabled = true
return true
}
override fun onMenuItemActionCollapse(item: MenuItem): Boolean {
isEnabled = false
return true
}
}

@ -20,6 +20,8 @@ import com.google.android.material.R as materialR
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback { class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback {
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(ActivityBrowserBinding.inflate(layoutInflater)) setContentView(ActivityBrowserBinding.inflate(layoutInflater))
@ -33,6 +35,8 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
} }
binding.webView.webViewClient = BrowserClient(this) binding.webView.webViewClient = BrowserClient(this)
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar) binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
onBackPressedCallback = WebViewBackPressedCallback(binding.webView)
onBackPressedDispatcher.addCallback(onBackPressedCallback)
if (savedInstanceState != null) { if (savedInstanceState != null) {
return return
} }
@ -84,14 +88,6 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack()
} else {
super.onBackPressed()
}
}
override fun onPause() { override fun onPause() {
binding.webView.onPause() binding.webView.onPause()
super.onPause() super.onPause()
@ -116,6 +112,10 @@ class BrowserActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallback
supportActionBar?.subtitle = subtitle supportActionBar?.subtitle = subtitle
} }
override fun onHistoryChanged() {
onBackPressedCallback.onHistoryChanged()
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.appbar.updatePadding( binding.appbar.updatePadding(
top = insets.top, top = insets.top,

@ -1,8 +1,8 @@
package org.koitharu.kotatsu.browser package org.koitharu.kotatsu.browser
interface BrowserCallback { interface BrowserCallback : OnHistoryChangedListener {
fun onLoadingStateChanged(isLoading: Boolean) fun onLoadingStateChanged(isLoading: Boolean)
fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) fun onTitleChanged(title: CharSequence, subtitle: CharSequence?)
} }

@ -4,7 +4,7 @@ import android.graphics.Bitmap
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient import android.webkit.WebViewClient
class BrowserClient(private val callback: BrowserCallback) : WebViewClient() { open class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
override fun onPageFinished(webView: WebView, url: String) { override fun onPageFinished(webView: WebView, url: String) {
super.onPageFinished(webView, url) super.onPageFinished(webView, url)
@ -20,4 +20,9 @@ class BrowserClient(private val callback: BrowserCallback) : WebViewClient() {
super.onPageCommitVisible(view, url) super.onPageCommitVisible(view, url)
callback.onTitleChanged(view.title.orEmpty(), url) callback.onTitleChanged(view.title.orEmpty(), url)
} }
}
override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
super.doUpdateVisitedHistory(view, url, isReload)
callback.onHistoryChanged()
}
}

@ -0,0 +1,6 @@
package org.koitharu.kotatsu.browser
fun interface OnHistoryChangedListener {
fun onHistoryChanged()
}

@ -0,0 +1,21 @@
package org.koitharu.kotatsu.browser
import android.webkit.WebView
import androidx.activity.OnBackPressedCallback
class WebViewBackPressedCallback(
private val webView: WebView,
) : OnBackPressedCallback(false), OnHistoryChangedListener {
init {
onHistoryChanged()
}
override fun handleOnBackPressed() {
webView.goBack()
}
override fun onHistoryChanged() {
isEnabled = webView.canGoBack()
}
}

@ -1,8 +1,14 @@
package org.koitharu.kotatsu.browser.cloudflare package org.koitharu.kotatsu.browser.cloudflare
interface CloudFlareCallback { import org.koitharu.kotatsu.browser.BrowserCallback
interface CloudFlareCallback : BrowserCallback {
override fun onLoadingStateChanged(isLoading: Boolean) = Unit
override fun onTitleChanged(title: CharSequence, subtitle: CharSequence?) = Unit
fun onPageLoaded() fun onPageLoaded()
fun onCheckPassed() fun onCheckPassed()
} }

@ -2,8 +2,8 @@ package org.koitharu.kotatsu.browser.cloudflare
import android.graphics.Bitmap import android.graphics.Bitmap
import android.webkit.WebView import android.webkit.WebView
import android.webkit.WebViewClient
import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl
import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
private const val CF_CLEARANCE = "cf_clearance" private const val CF_CLEARANCE = "cf_clearance"
@ -12,22 +12,22 @@ class CloudFlareClient(
private val cookieJar: MutableCookieJar, private val cookieJar: MutableCookieJar,
private val callback: CloudFlareCallback, private val callback: CloudFlareCallback,
private val targetUrl: String, private val targetUrl: String,
) : WebViewClient() { ) : BrowserClient(callback) {
private val oldClearance = getClearance() private val oldClearance = getClearance()
override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon) super.onPageStarted(view, url, favicon)
checkClearance() checkClearance()
} }
override fun onPageCommitVisible(view: WebView?, url: String?) { override fun onPageCommitVisible(view: WebView, url: String?) {
super.onPageCommitVisible(view, url) super.onPageCommitVisible(view, url)
callback.onPageLoaded() callback.onPageLoaded()
} }
override fun onPageFinished(view: WebView?, url: String?) { override fun onPageFinished(webView: WebView, url: String) {
super.onPageFinished(view, url) super.onPageFinished(webView, url)
callback.onPageLoaded() callback.onPageLoaded()
} }

@ -8,12 +8,14 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.webkit.CookieManager import android.webkit.CookieManager
import android.webkit.WebSettings import android.webkit.WebSettings
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import okhttp3.Headers import okhttp3.Headers
import org.koitharu.kotatsu.base.ui.AlertDialogFragment import org.koitharu.kotatsu.base.ui.AlertDialogFragment
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar import org.koitharu.kotatsu.core.network.cookies.MutableCookieJar
@ -31,6 +33,8 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
@Inject @Inject
lateinit var cookieJar: MutableCookieJar lateinit var cookieJar: MutableCookieJar
private var onBackPressedCallback: WebViewBackPressedCallback? = null
override fun onInflateView( override fun onInflateView(
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
@ -58,6 +62,7 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
override fun onDestroyView() { override fun onDestroyView() {
binding.webView.stopLoading() binding.webView.stopLoading()
binding.webView.destroy() binding.webView.destroy()
onBackPressedCallback = null
super.onDestroyView() super.onDestroyView()
} }
@ -65,6 +70,13 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
return super.onBuildDialog(builder).setNegativeButton(android.R.string.cancel, null) return super.onBuildDialog(builder).setNegativeButton(android.R.string.cancel, null)
} }
override fun onDialogCreated(dialog: AlertDialog) {
super.onDialogCreated(dialog)
onBackPressedCallback = WebViewBackPressedCallback(binding.webView).also {
dialog.onBackPressedDispatcher.addCallback(it)
}
}
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
binding.webView.onResume() binding.webView.onResume()
@ -89,6 +101,10 @@ class CloudFlareDialog : AlertDialogFragment<FragmentCloudflareBinding>(), Cloud
dismissAllowingStateLoss() dismissAllowingStateLoss()
} }
override fun onHistoryChanged() {
onBackPressedCallback?.onHistoryChanged()
}
companion object { companion object {
const val TAG = "CloudFlareDialog" const val TAG = "CloudFlareDialog"

@ -1,34 +1,29 @@
package org.koitharu.kotatsu.list.ui.filter package org.koitharu.kotatsu.list.ui.filter
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.* import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import androidx.recyclerview.widget.AsyncListDiffer import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.util.CollapseActionViewCallback
import org.koitharu.kotatsu.databinding.SheetFilterBinding import org.koitharu.kotatsu.databinding.SheetFilterBinding
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
import org.koitharu.kotatsu.utils.ext.isScrolledToTop
import org.koitharu.kotatsu.utils.ext.parentFragmentViewModels import org.koitharu.kotatsu.utils.ext.parentFragmentViewModels
class FilterBottomSheet : class FilterBottomSheet :
BaseBottomSheet<SheetFilterBinding>(), BaseBottomSheet<SheetFilterBinding>(),
MenuItem.OnActionExpandListener, MenuItem.OnActionExpandListener,
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,
DialogInterface.OnKeyListener,
AsyncListDiffer.ListListener<FilterItem> { AsyncListDiffer.ListListener<FilterItem> {
private val viewModel by parentFragmentViewModels<RemoteListViewModel>() private val viewModel by parentFragmentViewModels<RemoteListViewModel>()
private var collapsibleActionViewCallback: CollapseActionViewCallback? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).also {
it.setOnKeyListener(this)
}
}
override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding { override fun onInflateView(inflater: LayoutInflater, container: ViewGroup?): SheetFilterBinding {
return SheetFilterBinding.inflate(inflater, container, false) return SheetFilterBinding.inflate(inflater, container, false)
@ -42,8 +37,14 @@ class FilterBottomSheet :
initOptionsMenu() initOptionsMenu()
} }
override fun onDestroyView() {
super.onDestroyView()
collapsibleActionViewCallback = null
}
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
setExpanded(isExpanded = true, isLocked = true) setExpanded(isExpanded = true, isLocked = true)
collapsibleActionViewCallback?.onMenuItemActionExpand(item)
return true return true
} }
@ -51,6 +52,7 @@ class FilterBottomSheet :
val searchView = (item.actionView as? SearchView) ?: return false val searchView = (item.actionView as? SearchView) ?: return false
searchView.setQuery("", false) searchView.setQuery("", false)
searchView.post { setExpanded(isExpanded = false, isLocked = false) } searchView.post { setExpanded(isExpanded = false, isLocked = false) }
collapsibleActionViewCallback?.onMenuItemActionCollapse(item)
return true return true
} }
@ -61,19 +63,6 @@ class FilterBottomSheet :
return true return true
} }
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
val menuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search) ?: return false
if (menuItem.isActionViewExpanded) {
if (event?.action == KeyEvent.ACTION_UP) {
menuItem.collapseActionView()
}
return true
}
}
return false
}
override fun onCurrentListChanged(previousList: MutableList<FilterItem>, currentList: MutableList<FilterItem>) { override fun onCurrentListChanged(previousList: MutableList<FilterItem>, currentList: MutableList<FilterItem>) {
if (currentList.size > previousList.size && view != null) { if (currentList.size > previousList.size && view != null) {
(binding.recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0) (binding.recyclerView.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(0, 0)
@ -81,13 +70,16 @@ class FilterBottomSheet :
} }
private fun initOptionsMenu() { private fun initOptionsMenu() {
binding.headerBar.toolbar.inflateMenu(R.menu.opt_filter) binding.headerBar.inflateMenu(R.menu.opt_filter)
val searchMenuItem = binding.headerBar.toolbar.menu.findItem(R.id.action_search) val searchMenuItem = binding.headerBar.menu.findItem(R.id.action_search)
searchMenuItem.setOnActionExpandListener(this) searchMenuItem.setOnActionExpandListener(this)
val searchView = searchMenuItem.actionView as SearchView val searchView = searchMenuItem.actionView as SearchView
searchView.setOnQueryTextListener(this) searchView.setOnQueryTextListener(this)
searchView.setIconifiedByDefault(false) searchView.setIconifiedByDefault(false)
searchView.queryHint = searchMenuItem.title searchView.queryHint = searchMenuItem.title
collapsibleActionViewCallback = CollapseActionViewCallback(searchMenuItem).also {
onBackPressedDispatcher.addCallback(it)
}
} }
companion object { companion object {

@ -7,6 +7,7 @@ import android.os.Bundle
import android.util.SparseIntArray import android.util.SparseIntArray
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResultCallback import androidx.activity.result.ActivityResultCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
@ -84,6 +85,7 @@ class MainActivity :
private val viewModel by viewModels<MainViewModel>() private val viewModel by viewModels<MainViewModel>()
private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>() private val searchSuggestionViewModel by viewModels<SearchSuggestionViewModel>()
private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback()) private val voiceInputLauncher = registerForActivityResult(VoiceInputContract(), VoiceInputCallback())
private val closeSearchCallback = CloseSearchCallback()
private lateinit var navigationDelegate: MainNavigationDelegate private lateinit var navigationDelegate: MainNavigationDelegate
override val appBar: AppBarLayout override val appBar: AppBarLayout
@ -121,8 +123,9 @@ class MainActivity :
navigationDelegate.addOnFragmentChangedListener(this) navigationDelegate.addOnFragmentChangedListener(this)
navigationDelegate.onCreate(savedInstanceState) navigationDelegate.onCreate(savedInstanceState)
onBackPressedDispatcher.addCallback(navigationDelegate)
onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container)) onBackPressedDispatcher.addCallback(ExitCallback(this, binding.container))
onBackPressedDispatcher.addCallback(navigationDelegate)
onBackPressedDispatcher.addCallback(closeSearchCallback)
if (savedInstanceState == null) { if (savedInstanceState == null) {
onFirstStart() onFirstStart()
@ -142,21 +145,6 @@ class MainActivity :
adjustSearchUI(isSearchOpened(), animate = false) adjustSearchUI(isSearchOpened(), animate = false)
} }
override fun onBackPressed() {
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
binding.searchView.clearFocus()
when {
fragment != null -> supportFragmentManager.commit {
setReorderingAllowed(true)
remove(fragment)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
runOnCommit { onSearchClosed() }
}
else -> super.onBackPressed()
}
}
override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) { override fun onFragmentChanged(fragment: Fragment, fromUser: Boolean) {
adjustFabVisibility(topFragment = fragment) adjustFabVisibility(topFragment = fragment)
if (fromUser) { if (fromUser) {
@ -300,11 +288,13 @@ class MainActivity :
private fun onSearchOpened() { private fun onSearchOpened() {
adjustSearchUI(isOpened = true, animate = true) adjustSearchUI(isOpened = true, animate = true)
closeSearchCallback.isEnabled = true
} }
private fun onSearchClosed() { private fun onSearchClosed() {
binding.searchView.hideKeyboard() binding.searchView.hideKeyboard()
adjustSearchUI(isOpened = false, animate = true) adjustSearchUI(isOpened = false, animate = true)
closeSearchCallback.isEnabled = false
} }
private fun showNav(visible: Boolean) { private fun showNav(visible: Boolean) {
@ -406,4 +396,23 @@ class MainActivity :
} }
} }
} }
private inner class CloseSearchCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
val fragment = supportFragmentManager.findFragmentByTag(TAG_SEARCH)
binding.searchView.clearFocus()
if (fragment == null) {
// this should not happen but who knows
isEnabled = false
return
}
supportFragmentManager.commit {
setReorderingAllowed(true)
remove(fragment)
setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
runOnCommit { onSearchClosed() }
}
}
}
} }

@ -1,9 +1,6 @@
package org.koitharu.kotatsu.scrobbling.common.ui.selector package org.koitharu.kotatsu.scrobbling.common.ui.selector
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -20,6 +17,7 @@ import org.koitharu.kotatsu.base.domain.MangaIntent
import org.koitharu.kotatsu.base.ui.BaseBottomSheet import org.koitharu.kotatsu.base.ui.BaseBottomSheet
import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener import org.koitharu.kotatsu.base.ui.list.OnListItemClickListener
import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener import org.koitharu.kotatsu.base.ui.list.PaginationScrollListener
import org.koitharu.kotatsu.base.ui.util.CollapseActionViewCallback
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding import org.koitharu.kotatsu.databinding.SheetScrobblingSelectorBinding
import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener import org.koitharu.kotatsu.list.ui.adapter.ListStateHolderListener
@ -43,7 +41,6 @@ class ScrobblingSelectorBottomSheet :
View.OnClickListener, View.OnClickListener,
MenuItem.OnActionExpandListener, MenuItem.OnActionExpandListener,
SearchView.OnQueryTextListener, SearchView.OnQueryTextListener,
DialogInterface.OnKeyListener,
TabLayout.OnTabSelectedListener, TabLayout.OnTabSelectedListener,
ListStateHolderListener { ListStateHolderListener {
@ -53,6 +50,8 @@ class ScrobblingSelectorBottomSheet :
@Inject @Inject
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
private var collapsibleActionViewCallback: CollapseActionViewCallback? = null
private val viewModel by assistedViewModels { private val viewModel by assistedViewModels {
viewModelFactory.create( viewModelFactory.create(
requireArguments().requireParcelable<ParcelableManga>(MangaIntent.KEY_MANGA).manga, requireArguments().requireParcelable<ParcelableManga>(MangaIntent.KEY_MANGA).manga,
@ -63,12 +62,6 @@ class ScrobblingSelectorBottomSheet :
return SheetScrobblingSelectorBinding.inflate(inflater, container, false) return SheetScrobblingSelectorBinding.inflate(inflater, container, false)
} }
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).also {
it.setOnKeyListener(this)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this, this) val listAdapter = ScrobblerSelectorAdapter(viewLifecycleOwner, coil, this, this)
@ -102,6 +95,11 @@ class ScrobblingSelectorBottomSheet :
} }
} }
override fun onDestroyView() {
super.onDestroyView()
collapsibleActionViewCallback = null
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_done -> viewModel.onDoneClick() R.id.button_done -> viewModel.onDoneClick()
@ -126,6 +124,7 @@ class ScrobblingSelectorBottomSheet :
override fun onMenuItemActionExpand(item: MenuItem): Boolean { override fun onMenuItemActionExpand(item: MenuItem): Boolean {
setExpanded(isExpanded = true, isLocked = true) setExpanded(isExpanded = true, isLocked = true)
collapsibleActionViewCallback?.onMenuItemActionExpand(item)
return true return true
} }
@ -133,6 +132,7 @@ class ScrobblingSelectorBottomSheet :
val searchView = (item.actionView as? SearchView) ?: return false val searchView = (item.actionView as? SearchView) ?: return false
searchView.setQuery("", false) searchView.setQuery("", false)
searchView.post { setExpanded(isExpanded = false, isLocked = false) } searchView.post { setExpanded(isExpanded = false, isLocked = false) }
collapsibleActionViewCallback?.onMenuItemActionCollapse(item)
return true return true
} }
@ -147,19 +147,6 @@ class ScrobblingSelectorBottomSheet :
override fun onQueryTextChange(newText: String?): Boolean = false override fun onQueryTextChange(newText: String?): Boolean = false
override fun onKey(dialog: DialogInterface?, keyCode: Int, event: KeyEvent?): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK) {
val menuItem = binding.headerBar.menu.findItem(R.id.action_search) ?: return false
if (menuItem.isActionViewExpanded) {
if (event?.action == KeyEvent.ACTION_UP) {
menuItem.collapseActionView()
}
return true
}
}
return false
}
override fun onTabSelected(tab: TabLayout.Tab) { override fun onTabSelected(tab: TabLayout.Tab) {
viewModel.setScrobblerIndex(tab.position) viewModel.setScrobblerIndex(tab.position)
} }
@ -193,6 +180,9 @@ class ScrobblingSelectorBottomSheet :
searchView.setOnQueryTextListener(this) searchView.setOnQueryTextListener(this)
searchView.setIconifiedByDefault(false) searchView.setIconifiedByDefault(false)
searchView.queryHint = searchMenuItem.title searchView.queryHint = searchMenuItem.title
collapsibleActionViewCallback = CollapseActionViewCallback(searchMenuItem).also {
onBackPressedDispatcher.addCallback(it)
}
} }
private fun initTabs() { private fun initTabs() {

@ -17,6 +17,7 @@ import org.koitharu.kotatsu.base.ui.BaseActivity
import org.koitharu.kotatsu.browser.BrowserCallback import org.koitharu.kotatsu.browser.BrowserCallback
import org.koitharu.kotatsu.browser.BrowserClient import org.koitharu.kotatsu.browser.BrowserClient
import org.koitharu.kotatsu.browser.ProgressChromeClient import org.koitharu.kotatsu.browser.ProgressChromeClient
import org.koitharu.kotatsu.browser.WebViewBackPressedCallback
import org.koitharu.kotatsu.core.network.CommonHeaders import org.koitharu.kotatsu.core.network.CommonHeaders
import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor import org.koitharu.kotatsu.core.network.CommonHeadersInterceptor
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
@ -34,6 +35,7 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
@Inject @Inject
lateinit var mangaRepositoryFactory: MangaRepository.Factory lateinit var mangaRepositoryFactory: MangaRepository.Factory
private lateinit var onBackPressedCallback: WebViewBackPressedCallback
private lateinit var authProvider: MangaParserAuthProvider private lateinit var authProvider: MangaParserAuthProvider
@SuppressLint("SetJavaScriptEnabled") @SuppressLint("SetJavaScriptEnabled")
@ -66,6 +68,8 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
} }
binding.webView.webViewClient = BrowserClient(this) binding.webView.webViewClient = BrowserClient(this)
binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar) binding.webView.webChromeClient = ProgressChromeClient(binding.progressBar)
onBackPressedCallback = WebViewBackPressedCallback(binding.webView)
onBackPressedDispatcher.addCallback(onBackPressedCallback)
if (savedInstanceState != null) { if (savedInstanceState != null) {
return return
} }
@ -103,14 +107,6 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
override fun onBackPressed() {
if (binding.webView.canGoBack()) {
binding.webView.goBack()
} else {
super.onBackPressed()
}
}
override fun onPause() { override fun onPause() {
binding.webView.onPause() binding.webView.onPause()
super.onPause() super.onPause()
@ -135,6 +131,10 @@ class SourceAuthActivity : BaseActivity<ActivityBrowserBinding>(), BrowserCallba
supportActionBar?.subtitle = subtitle supportActionBar?.subtitle = subtitle
} }
override fun onHistoryChanged() {
onBackPressedCallback.onHistoryChanged()
}
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
binding.appbar.updatePadding(top = insets.top) binding.appbar.updatePadding(top = insets.top)
binding.webView.updatePadding(bottom = insets.bottom) binding.webView.updatePadding(bottom = insets.bottom)

@ -8,6 +8,7 @@ import android.text.Editable
import android.text.TextWatcher import android.text.TextWatcher
import android.view.View import android.view.View
import android.widget.Button import android.widget.Button
import androidx.activity.OnBackPressedCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.isGone import androidx.core.view.isGone
@ -28,6 +29,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null private var accountAuthenticatorResponse: AccountAuthenticatorResponse? = null
private var resultBundle: Bundle? = null private var resultBundle: Bundle? = null
private val pageBackCallback = PageBackCallback()
private val viewModel by viewModels<SyncAuthViewModel>() private val viewModel by viewModels<SyncAuthViewModel>()
@ -44,9 +46,13 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
binding.editEmail.addTextChangedListener(EmailTextWatcher(binding.buttonNext)) binding.editEmail.addTextChangedListener(EmailTextWatcher(binding.buttonNext))
binding.editPassword.addTextChangedListener(PasswordTextWatcher(binding.buttonDone)) binding.editPassword.addTextChangedListener(PasswordTextWatcher(binding.buttonDone))
onBackPressedDispatcher.addCallback(pageBackCallback)
viewModel.onTokenObtained.observe(this, ::onTokenReceived) viewModel.onTokenObtained.observe(this, ::onTokenReceived)
viewModel.onError.observe(this, ::onError) viewModel.onError.observe(this, ::onError)
viewModel.isLoading.observe(this, ::onLoadingStateChanged) viewModel.isLoading.observe(this, ::onLoadingStateChanged)
pageBackCallback.update()
} }
override fun onWindowInsetsChanged(insets: Insets) { override fun onWindowInsetsChanged(insets: Insets) {
@ -59,27 +65,23 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
) )
} }
@Suppress("DEPRECATION", "OVERRIDE_DEPRECATION")
override fun onBackPressed() {
if (binding.switcher.isVisible && binding.switcher.displayedChild > 0) {
binding.switcher.showPrevious()
} else {
super.onBackPressed()
}
}
override fun onClick(v: View) { override fun onClick(v: View) {
when (v.id) { when (v.id) {
R.id.button_cancel -> { R.id.button_cancel -> {
setResult(RESULT_CANCELED) setResult(RESULT_CANCELED)
finish() finish()
} }
R.id.button_next -> { R.id.button_next -> {
binding.switcher.showNext() binding.switcher.showNext()
pageBackCallback.update()
} }
R.id.button_back -> { R.id.button_back -> {
binding.switcher.showPrevious() binding.switcher.showPrevious()
pageBackCallback.update()
} }
R.id.button_done -> { R.id.button_done -> {
viewModel.obtainToken( viewModel.obtainToken(
email = binding.editEmail.text.toString(), email = binding.editEmail.text.toString(),
@ -105,6 +107,7 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
TransitionManager.beginDelayedTransition(binding.root, Fade()) TransitionManager.beginDelayedTransition(binding.root, Fade())
binding.switcher.isGone = isLoading binding.switcher.isGone = isLoading
binding.layoutProgress.isVisible = isLoading binding.layoutProgress.isVisible = isLoading
pageBackCallback.update()
} }
private fun onError(error: Throwable) { private fun onError(error: Throwable) {
@ -161,4 +164,16 @@ class SyncAuthActivity : BaseActivity<ActivitySyncAuthBinding>(), View.OnClickLi
button.isEnabled = text != null && text.length >= 4 button.isEnabled = text != null && text.length >= 4
} }
} }
private inner class PageBackCallback : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
binding.switcher.showPrevious()
update()
}
fun update() {
isEnabled = binding.switcher.isVisible && binding.switcher.displayedChild > 0
}
}
} }

Loading…
Cancel
Save