2019-03-20 16:29:04 +01:00
|
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
|
|
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
|
|
file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
|
2019-03-19 17:13:48 +01:00
|
|
|
package org.mozilla.fenix.quickactionsheet
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
import android.util.AttributeSet
|
|
|
|
import android.view.View
|
|
|
|
import android.widget.LinearLayout
|
|
|
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
|
|
import androidx.core.widget.NestedScrollView
|
|
|
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
|
|
import mozilla.components.browser.toolbar.BrowserToolbar
|
|
|
|
import org.mozilla.fenix.R
|
2019-05-14 17:14:41 +02:00
|
|
|
import android.os.Bundle
|
|
|
|
import android.view.accessibility.AccessibilityEvent
|
|
|
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
|
|
import android.widget.ImageButton
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Dispatchers.Main
|
|
|
|
import kotlinx.coroutines.Job
|
|
|
|
import kotlinx.coroutines.delay
|
2019-03-20 16:55:24 +01:00
|
|
|
import org.mozilla.fenix.utils.Settings
|
2019-05-14 17:14:41 +02:00
|
|
|
import kotlin.coroutines.CoroutineContext
|
2019-03-19 17:13:48 +01:00
|
|
|
|
2019-04-26 20:35:57 +02:00
|
|
|
const val POSITION_SNAP_BUFFER = 1f
|
|
|
|
|
2019-03-19 17:13:48 +01:00
|
|
|
class QuickActionSheet @JvmOverloads constructor(
|
|
|
|
context: Context,
|
|
|
|
attrs: AttributeSet? = null,
|
|
|
|
defStyle: Int = 0,
|
|
|
|
defStyleRes: Int = 0
|
2019-05-14 17:14:41 +02:00
|
|
|
) : LinearLayout(context, attrs, defStyle, defStyleRes), CoroutineScope {
|
|
|
|
|
|
|
|
private lateinit var job: Job
|
|
|
|
override val coroutineContext: CoroutineContext
|
|
|
|
get() = Dispatchers.Main + job
|
|
|
|
|
|
|
|
private lateinit var handle: ImageButton
|
|
|
|
private lateinit var linearLayout: LinearLayout
|
|
|
|
private lateinit var quickActionSheetBehavior: QuickActionSheetBehavior
|
2019-03-19 17:13:48 +01:00
|
|
|
|
|
|
|
init {
|
2019-03-20 17:28:36 +01:00
|
|
|
inflate(getContext(), R.layout.layout_quick_action_sheet, this)
|
2019-03-19 22:14:55 +01:00
|
|
|
}
|
|
|
|
|
2019-03-21 15:58:24 +01:00
|
|
|
override fun onAttachedToWindow() {
|
|
|
|
super.onAttachedToWindow()
|
2019-05-14 17:14:41 +02:00
|
|
|
job = Job()
|
|
|
|
handle = findViewById(R.id.quick_action_sheet_handle)
|
|
|
|
linearLayout = findViewById(R.id.quick_action_sheet)
|
|
|
|
quickActionSheetBehavior = BottomSheetBehavior.from(linearLayout.parent as View) as QuickActionSheetBehavior
|
2019-05-22 19:02:10 +02:00
|
|
|
quickActionSheetBehavior.isHideable = false
|
2019-03-19 22:14:55 +01:00
|
|
|
setupHandle()
|
2019-03-19 17:13:48 +01:00
|
|
|
}
|
|
|
|
|
2019-05-14 17:14:41 +02:00
|
|
|
override fun onDetachedFromWindow() {
|
|
|
|
super.onDetachedFromWindow()
|
|
|
|
job.cancel()
|
|
|
|
}
|
2019-03-20 17:28:36 +01:00
|
|
|
|
2019-05-14 17:14:41 +02:00
|
|
|
private fun setupHandle() {
|
2019-03-19 21:26:36 +01:00
|
|
|
handle.setOnClickListener {
|
2019-05-14 17:14:41 +02:00
|
|
|
quickActionSheetBehavior.state = when (quickActionSheetBehavior.state) {
|
|
|
|
BottomSheetBehavior.STATE_EXPANDED -> BottomSheetBehavior.STATE_COLLAPSED
|
|
|
|
else -> BottomSheetBehavior.STATE_EXPANDED
|
|
|
|
}
|
2019-03-19 21:26:36 +01:00
|
|
|
}
|
2019-03-19 17:13:48 +01:00
|
|
|
|
2019-03-27 19:14:36 +01:00
|
|
|
handle.setAccessibilityDelegate(HandleAccessibilityDelegate(quickActionSheetBehavior))
|
2019-03-19 17:13:48 +01:00
|
|
|
}
|
|
|
|
|
2019-05-14 17:14:41 +02:00
|
|
|
fun bounceSheet() {
|
|
|
|
launch(Main) {
|
|
|
|
delay(BOUNCE_ANIMATION_DELAY_LENGTH)
|
|
|
|
quickActionSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
|
|
|
|
delay(BOUNCE_ANIMATION_PAUSE_LENGTH)
|
|
|
|
quickActionSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
|
2019-03-21 18:44:50 +01:00
|
|
|
}
|
2019-05-14 17:14:41 +02:00
|
|
|
Settings.getInstance(context).incrementAutomaticBounceQuickActionSheetCount()
|
2019-03-19 22:14:55 +01:00
|
|
|
}
|
2019-03-20 16:29:04 +01:00
|
|
|
|
2019-03-27 19:14:36 +01:00
|
|
|
class HandleAccessibilityDelegate(
|
|
|
|
private val quickActionSheetBehavior: QuickActionSheetBehavior
|
|
|
|
) : View.AccessibilityDelegate() {
|
|
|
|
private var finalState = BottomSheetBehavior.STATE_COLLAPSED
|
2019-05-14 17:14:41 +02:00
|
|
|
get() = when (quickActionSheetBehavior.state) {
|
|
|
|
BottomSheetBehavior.STATE_EXPANDED,
|
|
|
|
BottomSheetBehavior.STATE_HIDDEN,
|
|
|
|
BottomSheetBehavior.STATE_COLLAPSED -> {
|
|
|
|
quickActionSheetBehavior.state
|
2019-03-27 19:14:36 +01:00
|
|
|
}
|
2019-05-14 17:14:41 +02:00
|
|
|
else -> field
|
|
|
|
}
|
|
|
|
set(value) {
|
|
|
|
field = value
|
|
|
|
quickActionSheetBehavior.state = value
|
|
|
|
}
|
2019-03-27 19:14:36 +01:00
|
|
|
|
|
|
|
override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
|
|
|
|
when (action) {
|
|
|
|
AccessibilityNodeInfo.ACTION_CLICK -> {
|
|
|
|
finalState = when (quickActionSheetBehavior.state) {
|
|
|
|
BottomSheetBehavior.STATE_EXPANDED -> BottomSheetBehavior.STATE_COLLAPSED
|
|
|
|
else -> BottomSheetBehavior.STATE_EXPANDED
|
|
|
|
}
|
|
|
|
}
|
|
|
|
AccessibilityNodeInfo.ACTION_COLLAPSE ->
|
|
|
|
finalState = BottomSheetBehavior.STATE_COLLAPSED
|
|
|
|
AccessibilityNodeInfo.ACTION_EXPAND ->
|
|
|
|
finalState = BottomSheetBehavior.STATE_EXPANDED
|
|
|
|
else -> return super.performAccessibilityAction(host, action, args)
|
|
|
|
}
|
|
|
|
|
|
|
|
host?.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED)
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onInitializeAccessibilityNodeInfo(host: View?, info: AccessibilityNodeInfo?) {
|
|
|
|
super.onInitializeAccessibilityNodeInfo(host, info)
|
2019-05-14 17:14:41 +02:00
|
|
|
info?.addAction(when (finalState) {
|
|
|
|
BottomSheetBehavior.STATE_COLLAPSED,
|
|
|
|
BottomSheetBehavior.STATE_HIDDEN -> AccessibilityNodeInfo.AccessibilityAction.ACTION_EXPAND
|
|
|
|
else -> AccessibilityNodeInfo.AccessibilityAction.ACTION_COLLAPSE
|
|
|
|
})
|
2019-03-27 19:14:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-20 16:29:04 +01:00
|
|
|
companion object {
|
2019-05-14 17:14:41 +02:00
|
|
|
const val BOUNCE_ANIMATION_DELAY_LENGTH = 1000L
|
|
|
|
const val BOUNCE_ANIMATION_PAUSE_LENGTH = 2000L
|
2019-03-20 16:29:04 +01:00
|
|
|
}
|
2019-03-19 17:13:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
@Suppress("unused") // Referenced from XML
|
|
|
|
class QuickActionSheetBehavior(
|
|
|
|
context: Context,
|
|
|
|
attrs: AttributeSet
|
|
|
|
) : BottomSheetBehavior<NestedScrollView>(context, attrs) {
|
2019-04-25 20:30:08 +02:00
|
|
|
|
2019-03-19 17:13:48 +01:00
|
|
|
override fun layoutDependsOn(parent: CoordinatorLayout, child: NestedScrollView, dependency: View): Boolean {
|
|
|
|
if (dependency is BrowserToolbar) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
return super.layoutDependsOn(parent, child, dependency)
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onDependentViewChanged(
|
|
|
|
parent: CoordinatorLayout,
|
|
|
|
child: NestedScrollView,
|
|
|
|
dependency: View
|
|
|
|
): Boolean {
|
|
|
|
return if (dependency is BrowserToolbar) {
|
|
|
|
repositionQuickActionSheet(child, dependency)
|
|
|
|
true
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private fun repositionQuickActionSheet(quickActionSheetContainer: NestedScrollView, toolbar: BrowserToolbar) {
|
2019-04-26 20:35:57 +02:00
|
|
|
if (toolbar.translationY >= toolbar.height.toFloat() - POSITION_SNAP_BUFFER) {
|
2019-05-14 17:14:41 +02:00
|
|
|
state = STATE_HIDDEN
|
2019-04-26 20:35:57 +02:00
|
|
|
} else if (state == STATE_HIDDEN || state == STATE_SETTLING) {
|
|
|
|
state = STATE_COLLAPSED
|
|
|
|
}
|
2019-05-02 23:25:17 +02:00
|
|
|
quickActionSheetContainer.translationY = toolbar.translationY + toolbar.height * -1.0f
|
2019-03-19 17:13:48 +01:00
|
|
|
}
|
2019-03-20 16:29:04 +01:00
|
|
|
}
|