Skip to content

Commit

Permalink
Feat: Support predicative back gesture
Browse files Browse the repository at this point in the history
Fixes: #1103
  • Loading branch information
zhanghai committed Mar 4, 2024
1 parent c91754b commit a74b66d
Show file tree
Hide file tree
Showing 11 changed files with 139 additions and 48 deletions.
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
<application
android:allowBackup="true"
android:banner="@drawable/banner"
android:enableOnBackInvokedCallback="true"
android:fullBackupContent="true"
android:icon="@mipmap/launcher_icon"
android:label="@string/app_name"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,6 @@ class FileListActivity : AppActivity() {
return super.onKeyUp(keyCode, event)
}

override fun onBackPressed() {
if (fragment.onBackPressed()) {
return
}
super.onBackPressed()
}

companion object {
fun createViewIntent(path: Path): Intent =
FileListActivity::class.createIntent()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
Expand Down Expand Up @@ -81,12 +82,14 @@ import me.zhanghai.android.files.settings.Settings
import me.zhanghai.android.files.terminal.Terminal
import me.zhanghai.android.files.ui.AppBarLayoutExpandHackListener
import me.zhanghai.android.files.ui.CoordinatorAppBarLayout
import me.zhanghai.android.files.ui.DrawerLayoutOnBackPressedCallback
import me.zhanghai.android.files.ui.FixQueryChangeSearchView
import me.zhanghai.android.files.ui.OverlayToolbarActionMode
import me.zhanghai.android.files.ui.PersistentBarLayout
import me.zhanghai.android.files.ui.PersistentBarLayoutToolbarActionMode
import me.zhanghai.android.files.ui.PersistentDrawerLayout
import me.zhanghai.android.files.ui.ScrollingViewOnApplyWindowInsetsListener
import me.zhanghai.android.files.ui.SpeedDialViewOnBackPressedCallback
import me.zhanghai.android.files.ui.ThemedFastScroller
import me.zhanghai.android.files.ui.ToolbarActionMode
import me.zhanghai.android.files.util.DebouncedRunnable
Expand All @@ -95,6 +98,7 @@ import me.zhanghai.android.files.util.Loading
import me.zhanghai.android.files.util.ParcelableArgs
import me.zhanghai.android.files.util.Stateful
import me.zhanghai.android.files.util.Success
import me.zhanghai.android.files.util.addOnBackPressedCallback
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.checkSelfPermission
import me.zhanghai.android.files.util.copyText
Expand Down Expand Up @@ -248,6 +252,25 @@ class FileListFragment : Fragment(), BreadcrumbLayout.Listener, FileListAdapter.
true
}

val viewLifecycleOwner = viewLifecycleOwner
addOnBackPressedCallback(
object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
viewModel.navigateUp()
}
}
.also { callback ->
viewModel.breadcrumbLiveData.observe(viewLifecycleOwner) {
callback.isEnabled = viewModel.canNavigateUpBreadcrumb
}
}
)
addOnBackPressedCallback(overlayActionMode.onBackPressedCallback)
addOnBackPressedCallback(SpeedDialViewOnBackPressedCallback(binding.speedDialView))
binding.drawerLayout?.let {
addOnBackPressedCallback(DrawerLayoutOnBackPressedCallback(it))
}

if (!viewModel.hasTrail) {
var path = argsPath
val intent = args.intent
Expand Down Expand Up @@ -299,7 +322,6 @@ class FileListFragment : Fragment(), BreadcrumbLayout.Listener, FileListAdapter.
viewModel.pickOptions = pickOptions
}
}
val viewLifecycleOwner = viewLifecycleOwner
viewModel.currentPathLiveData.observe(viewLifecycleOwner) { onCurrentPathChanged(it) }
viewModel.searchViewExpandedLiveData.observe(viewLifecycleOwner) {
onSearchViewExpandedChanged(it)
Expand Down Expand Up @@ -526,26 +548,6 @@ class FileListFragment : Fragment(), BreadcrumbLayout.Listener, FileListAdapter.
return false
}

fun onBackPressed(): Boolean {
val drawerLayout = binding.drawerLayout
if (drawerLayout != null && drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawer(GravityCompat.START)
return true
}
if (binding.speedDialView.isOpen) {
binding.speedDialView.close()
return true
}
if (overlayActionMode.isActive) {
overlayActionMode.finish()
return true
}
if (viewModel.navigateUp(false)) {
return true
}
return false
}

private fun onPersistentDrawerOpenChanged(open: Boolean) {
binding.persistentDrawerLayout?.let {
if (open) {
Expand Down Expand Up @@ -687,7 +689,7 @@ class FileListFragment : Fragment(), BreadcrumbLayout.Listener, FileListAdapter.

private fun navigateUp() {
collapseSearchView()
viewModel.navigateUp(true)
viewModel.navigateUp()
}

private fun showNavigateToPathDialog() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,7 @@ class FileListViewModel : ViewModel() {

fun resetTo(path: Path) = trailLiveData.resetTo(path)

fun navigateUp(overrideBreadcrumb: Boolean): Boolean =
if (!overrideBreadcrumb && breadcrumbLiveData.valueCompat.selectedIndex == 0) {
false
} else {
trailLiveData.navigateUp()
}
fun navigateUp(): Boolean = trailLiveData.navigateUp()

val currentPathLiveData = trailLiveData.map { it.currentPath }
val currentPath: Path
Expand Down Expand Up @@ -102,6 +97,8 @@ class FileListViewModel : ViewModel() {
}

val breadcrumbLiveData: LiveData<BreadcrumbData> = BreadcrumbLiveData(trailLiveData)
val canNavigateUpBreadcrumb: Boolean
get() = breadcrumbLiveData.valueCompat.selectedIndex > 0

private val _viewTypeLiveData = FileViewTypeLiveData(currentPathLiveData)
val viewTypeLiveData: LiveData<FileViewType> = _viewTypeLiveData
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/

package me.zhanghai.android.files.ui

import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.drawerlayout.widget.DrawerLayout.SimpleDrawerListener

class DrawerLayoutOnBackPressedCallback(
private val drawerLayout: DrawerLayout,
private val gravity: Int = GravityCompat.START
) : OnBackPressedCallback(drawerLayout.isDrawerVisibleAndUnlocked(gravity)) {
init {
drawerLayout.addDrawerListener(
object : SimpleDrawerListener() {
override fun onDrawerOpened(drawerView: View) {
isEnabled = drawerLayout.isDrawerVisibleAndUnlocked(gravity)
}

override fun onDrawerClosed(drawerView: View) {
isEnabled = drawerLayout.isDrawerVisibleAndUnlocked(gravity)
}
}
)
}

override fun handleOnBackPressed() {
drawerLayout.closeDrawer(gravity)
}
}

private fun DrawerLayout.isDrawerVisibleAndUnlocked(gravity: Int): Boolean =
isDrawerVisible(gravity) && getDrawerLockMode(gravity) == DrawerLayout.LOCK_MODE_UNLOCKED
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2024 Hai Zhang <dreaming.in.code.zh@gmail.com>
* All Rights Reserved.
*/

package me.zhanghai.android.files.ui

import androidx.activity.OnBackPressedCallback
import com.leinardi.android.speeddial.SpeedDialView

class SpeedDialViewOnBackPressedCallback(
private val speedDialView: SpeedDialView
) : OnBackPressedCallback(speedDialView.isOpen) {
init {
speedDialView.setOnChangeListener(
object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean = false

override fun onToggleChanged(isOpen: Boolean) {
isEnabled = speedDialView.isOpen
}
}
)
}

override fun handleOnBackPressed() {
speedDialView.close()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ import me.zhanghai.android.files.util.shortAnimTime
import me.zhanghai.android.files.util.withModulatedAlpha

class ThemedSpeedDialView : SpeedDialView {
private var onChangeListener: OnChangeListener? = null

private var mainFabAnimator: Animator? = null

constructor(context: Context) : super(context)
Expand Down Expand Up @@ -86,8 +88,9 @@ class ThemedSpeedDialView : SpeedDialView {
}
mainFabAnimationRotateAngle = 0f
setMainFabClosedDrawable(mainFabDrawable)
setOnChangeListener(object : OnChangeListener {
override fun onMainActionSelected(): Boolean = false
super.setOnChangeListener(object : OnChangeListener {
override fun onMainActionSelected(): Boolean =
onChangeListener?.onMainActionSelected() ?: false

override fun onToggleChanged(isOpen: Boolean) {
mainFabAnimator?.cancel()
Expand All @@ -99,10 +102,15 @@ class ThemedSpeedDialView : SpeedDialView {
})
start()
}
onChangeListener?.onToggleChanged(isOpen)
}
})
}

override fun setOnChangeListener(onChangeListener: OnChangeListener?) {
this.onChangeListener = onChangeListener
}

private fun createMainFabAnimator(isOpen: Boolean): Animator =
AnimatorSet().apply {
playTogether(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.graphics.drawable.Drawable
import android.view.Menu
import android.view.MenuItem
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.annotation.DrawableRes
import androidx.annotation.MenuRes
import androidx.annotation.StringRes
Expand All @@ -29,6 +30,12 @@ abstract class ToolbarActionMode(
}
}

val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
finish()
}
}

var navigationIcon: Drawable?
get() = toolbar.navigationIcon
set(value) {
Expand Down Expand Up @@ -78,6 +85,7 @@ abstract class ToolbarActionMode(

fun start(callback: Callback, animate: Boolean = true) {
this.callback = callback
onBackPressedCallback.isEnabled = true
show(bar, animate)
callback.onToolbarActionModeStarted(this)
}
Expand All @@ -87,6 +95,7 @@ abstract class ToolbarActionMode(
fun finish(animate: Boolean = true) {
val callback = callback ?: return
this.callback = null
onBackPressedCallback.isEnabled = false
toolbar.menu.close()
hide(bar, animate)
callback.onToolbarActionModeFinished(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import android.os.Bundle
import android.view.animation.Animation
import android.view.animation.Interpolator
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.annotation.AnimRes
import androidx.annotation.AnyRes
import androidx.annotation.ArrayRes
Expand All @@ -33,6 +34,10 @@ import me.zhanghai.android.files.compat.getColorCompat
import me.zhanghai.android.files.compat.getColorStateListCompat
import me.zhanghai.android.files.compat.getDrawableCompat

fun Fragment.addOnBackPressedCallback(callback: OnBackPressedCallback) {
requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, callback)
}

fun Fragment.checkSelfPermission(permission: String): Int =
requireContext().checkSelfPermissionCompat(permission)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,8 @@ class TextEditorActivity : AppActivity() {
}
}

override fun onBackPressed() {
if (fragment.onFinish()) {
return
}
super.onBackPressed()
}

override fun onSupportNavigateUp(): Boolean {
if (fragment.onFinish()) {
if (fragment.onSupportNavigateUp()) {
return true
}
return super.onSupportNavigateUp()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import android.view.MenuItem
import android.view.SubMenu
import android.view.View
import android.view.ViewGroup
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.core.widget.doAfterTextChanged
Expand All @@ -28,6 +29,7 @@ import me.zhanghai.android.files.ui.ThemedFastScroller
import me.zhanghai.android.files.util.ActionState
import me.zhanghai.android.files.util.DataState
import me.zhanghai.android.files.util.ParcelableArgs
import me.zhanghai.android.files.util.addOnBackPressedCallback
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.extraPath
import me.zhanghai.android.files.util.fadeInUnsafe
Expand All @@ -48,6 +50,8 @@ class TextEditorFragment : Fragment(), ConfirmReloadDialogFragment.Listener,

private val viewModel by viewModels { { TextEditorViewModel(argsFile) } }

private lateinit var onBackPressedCallback: OnBackPressedCallback

private var isSettingText = false

override fun onCreate(savedInstanceState: Bundle?) {
Expand All @@ -56,6 +60,18 @@ class TextEditorFragment : Fragment(), ConfirmReloadDialogFragment.Listener,
setHasOptionsMenu(true)

lifecycleScope.launchWhenStarted {
onBackPressedCallback = object : OnBackPressedCallback(false) {
override fun handleOnBackPressed() {
ConfirmCloseDialogFragment.show(this@TextEditorFragment)
}
}
launch {
viewModel.isTextChanged.collect {
onBackPressedCallback.isEnabled = viewModel.isTextChanged.value
}
}
addOnBackPressedCallback(onBackPressedCallback)

launch { viewModel.encoding.collect { onEncodingChanged(it) } }
launch { viewModel.textState.collect { onTextStateChanged(it) } }
launch { viewModel.isTextChanged.collect { onIsTextChangedChanged(it) } }
Expand Down Expand Up @@ -148,9 +164,9 @@ class TextEditorFragment : Fragment(), ConfirmReloadDialogFragment.Listener,
else -> super.onOptionsItemSelected(item)
}

fun onFinish(): Boolean {
if (viewModel.isTextChanged.value) {
ConfirmCloseDialogFragment.show(this)
fun onSupportNavigateUp(): Boolean {
if (onBackPressedCallback.isEnabled) {
onBackPressedCallback.handleOnBackPressed()
return true
}
return false
Expand Down

0 comments on commit a74b66d

Please sign in to comment.