@craft-native/android 0.0.221 → 0.0.222

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@craft-native/android",
3
- "version": "0.0.221",
3
+ "version": "0.0.222",
4
4
  "homepage": "https://github.com/craft-native/craft/tree/main/packages/android#readme",
5
5
  "repository": {
6
6
  "type": "git",
@@ -894,6 +894,7 @@ internal class CraftNativeChrome(context: Context, private val tabs: List<CraftN
894
894
  accessory.background = GradientDrawable().apply { setColor(surface); cornerRadius = dp(32).toFloat() }
895
895
  accessorySpec?.let { accessoryIcon.setImageDrawable(CraftNativeIcons.drawable(context, null, it.optString("symbol"), text)) }
896
896
  tabViews.forEachIndexed { index, (icon, label) ->
897
+ tabBar.getChildAt(index).isSelected = index == selectedTab
897
898
  val color = if (index == selectedTab) accent else muted
898
899
  val number = tabIconText[tabs[index].id]
899
900
  icon.setTextColor(if (number != null) Color.parseColor("#FF2D55") else color)
@@ -3780,6 +3780,8 @@ class MainActivity : AppCompatActivity() {
3780
3780
  private var tabSwipeTarget: Screen? = null
3781
3781
  private var tabSwipeTargetIndex = -1
3782
3782
  private var tabSwipeDirection = 0
3783
+ private var tabSwipeGeneration = 0
3784
+ private var tabSwipeSettling = false
3783
3785
  /** The app script's isolate (`nativeApp`), when the app has one. */
3784
3786
  private var appScreen: Screen? = null
3785
3787
  private val urlSchemes = mutableSetOf<String>()
@@ -3960,6 +3962,7 @@ class MainActivity : AppCompatActivity() {
3960
3962
  bars.onAccessory = { path -> navigateOpen(path) }
3961
3963
  bars.onButton = { id -> visibleScreen?.takeIf { it.active }?.let { send(it, "NAV_BUTTON", JSONObject().put("id", id)) } }
3962
3964
  bars.onTab = { index ->
3965
+ interruptTabSwipe()
3963
3966
  haptic()
3964
3967
  if (index == currentTab) popToRoot() else selectTab(index)
3965
3968
  }
@@ -3996,6 +3999,7 @@ class MainActivity : AppCompatActivity() {
3996
3999
  /** A tab chosen: its stack shows, its root screen opened the first time. */
3997
4000
  private fun selectTab(index: Int) {
3998
4001
  if (index !in tabStacks.indices) return
4002
+ interruptTabSwipe()
3999
4003
  currentTab = index
4000
4004
  screens = tabStacks[index]
4001
4005
  chrome?.selectTab(index)
@@ -4017,6 +4021,7 @@ class MainActivity : AppCompatActivity() {
4017
4021
 
4018
4022
  /** Start an interactive horizontal transition to the adjacent tab. */
4019
4023
  private fun beginTabSwipe(dx: Float, x: Float, y: Float): Boolean {
4024
+ if (tabSwipeSettling) interruptTabSwipe()
4020
4025
  if (tabSwipeTarget != null || screens.size != 1 || chrome?.tabSwipeEnabled != true) return false
4021
4026
  val direction = if (dx < 0) 1 else -1
4022
4027
  val targetIndex = currentTab + direction
@@ -4055,16 +4060,23 @@ class MainActivity : AppCompatActivity() {
4055
4060
  val host = chrome?.content ?: return
4056
4061
  val width = host.width.toFloat().coerceAtLeast(1f)
4057
4062
  val complete = !cancelled && kotlin.math.abs(dx) >= width * 0.28f
4063
+ val generation = ++tabSwipeGeneration
4064
+ val targetIndex = tabSwipeTargetIndex
4065
+ tabSwipeSettling = true
4058
4066
  val endOffset = if (complete) {
4059
4067
  if (tabSwipeDirection > 0) -width else width
4060
4068
  } else 0f
4061
4069
  val targetOffset = if (complete) 0f else if (tabSwipeDirection > 0) width else -width
4062
4070
  current.view.animate().translationX(endOffset).setDuration(180).start()
4063
4071
  target.view.animate().translationX(targetOffset).setDuration(180).withEndAction {
4072
+ // A later tab tap or gesture owns the display even if Android
4073
+ // delivers this animator's completion after it was cancelled.
4074
+ if (generation != tabSwipeGeneration || tabSwipeTarget !== target || visibleScreen !== current) return@withEndAction
4064
4075
  target.view.animate().setListener(null)
4076
+ tabSwipeSettling = false
4065
4077
  if (complete) {
4066
4078
  current.view.translationX = 0f
4067
- currentTab = tabSwipeTargetIndex
4079
+ currentTab = targetIndex
4068
4080
  screens = tabStacks[currentTab]
4069
4081
  tabSwipeTarget = null
4070
4082
  tabSwipeTargetIndex = -1
@@ -4082,6 +4094,24 @@ class MainActivity : AppCompatActivity() {
4082
4094
  }.start()
4083
4095
  }
4084
4096
 
4097
+ /** Restore the owning stack before a newer navigation action takes over. */
4098
+ private fun interruptTabSwipe() {
4099
+ val target = tabSwipeTarget ?: return
4100
+ ++tabSwipeGeneration
4101
+ tabSwipeTarget = null
4102
+ tabSwipeTargetIndex = -1
4103
+ tabSwipeDirection = 0
4104
+ tabSwipeSettling = false
4105
+ val current = visibleScreen
4106
+ current?.view?.animate()?.withEndAction(null)?.setListener(null)?.cancel()
4107
+ target.view.animate().withEndAction(null).setListener(null).cancel()
4108
+ current?.view?.translationX = 0f
4109
+ target.view.translationX = 0f
4110
+ if (target !== current) (target.view.parent as? ViewGroup)?.removeView(target.view)
4111
+ chrome?.selectTab(currentTab)
4112
+ current?.let(::applyChrome)
4113
+ }
4114
+
4085
4115
  /**
4086
4116
  * Returns true when the exact screen gesture point is over a horizontal
4087
4117
  * child that still has room in the gesture's direction. Screen coordinates
@@ -4172,6 +4202,7 @@ class MainActivity : AppCompatActivity() {
4172
4202
  }
4173
4203
 
4174
4204
  private fun openScreen(route: String?, params: JSONObject = JSONObject(), headless: Boolean = false, showImmediately: Boolean = true): Screen? {
4205
+ if (showImmediately && !headless) interruptTabSwipe()
4175
4206
  val engine = sandbox ?: return null
4176
4207
  val isolate = engine.createIsolate()
4177
4208
  val view = CraftNativeFlexLayout(this).apply {
@@ -6943,6 +6974,7 @@ class MainActivity : AppCompatActivity() {
6943
6974
 
6944
6975
  /** Back to the first screen in one move. */
6945
6976
  private fun popToRoot() {
6977
+ interruptTabSwipe()
6946
6978
  val stack = screens
6947
6979
  val removed = mutableListOf<Screen>()
6948
6980
  while (stack.size > 1) removed.add(stack.removeAt(stack.lastIndex))
@@ -6954,6 +6986,7 @@ class MainActivity : AppCompatActivity() {
6954
6986
 
6955
6987
  /** Back: the screen below in this tab, then the first tab, then out of the app. */
6956
6988
  private fun back() {
6989
+ interruptTabSwipe()
6957
6990
  // A previous back is still draining its screen's teardown messages.
6958
6991
  if (visibleScreen == null) return
6959
6992
  if (screens.size > 1) {
@@ -7281,6 +7314,7 @@ class MainActivity : AppCompatActivity() {
7281
7314
  }
7282
7315
 
7283
7316
  override fun onDestroy() {
7317
+ interruptTabSwipe()
7284
7318
  if (animationScaleObserverRegistered) contentResolver.unregisterContentObserver(animationScaleObserver)
7285
7319
  CraftNativeBackground.foreground = null
7286
7320
  backgroundWaiters.values.forEach { it(false) }