react-native-screens 5.0.0-alpha.2 → 5.0.0-alpha.3
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.swift +87 -0
- package/README.md +0 -7
- package/android/src/main/cpp/NativeProxy.cpp +30 -6
- package/android/src/main/cpp/NativeProxy.h +7 -1
- package/android/src/main/java/com/swmansion/rnscreens/common/container/Container.kt +8 -0
- package/android/src/main/java/com/swmansion/rnscreens/common/container/ContainerItem.kt +13 -1
- package/android/src/main/java/com/swmansion/rnscreens/common/text/ReactTextAppearance.kt +62 -0
- package/android/src/main/java/com/swmansion/rnscreens/common/text/TextAppearance.kt +20 -0
- package/android/src/main/java/com/swmansion/rnscreens/common/text/TextAppearanceDefaults.kt +65 -0
- package/android/src/main/java/com/swmansion/rnscreens/helpers/PropIconResolver.kt +17 -23
- package/android/src/main/java/com/swmansion/rnscreens/legacy/Screen.kt +3 -1
- package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStack.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetBehaviorController.kt +49 -18
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetDimensionsCoordinator.kt +65 -40
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetKeyboardCoordinator.kt +101 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetNativeDismissCoordinator.kt +0 -6
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetAvailableHeightProvider.kt +44 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetDialog.kt +35 -3
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetDialogManager.kt +33 -106
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/interfaces/FormSheetDialogEventEmitter.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/model/FormSheetDetents.kt +61 -15
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetAnimatorFactory.kt +8 -9
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetDimmingManager.kt +115 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetDismissalOrigin.kt +22 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentation.kt +173 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentationManager.kt +141 -49
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentationState.kt +1 -1
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetStackRegistry.kt +28 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/event/FormSheetDismissEvent.kt +29 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/host/FormSheetHostEventEmitter.kt +7 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/host/FormSheetHostViewManager.kt +2 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderApplicator.kt +369 -58
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderBackPressHandler.kt +11 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderCoordinatorLayout.kt +143 -98
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderAppBarLayout.kt +155 -23
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderContentScrimDrawable.kt +36 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderCollapsedTitleGravityMode.kt +6 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfig.kt +170 -167
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigViewManager.kt +385 -12
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationObserver.kt +0 -4
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationProviding.kt +32 -3
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderDelegate.kt +0 -7
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderInvalidationFlags.kt +7 -2
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuApplicator.kt +27 -280
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuController.kt +448 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuDelegate.kt +10 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuMapper.kt +28 -20
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuConfig.kt +3 -55
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuGroupMetadata.kt +1 -10
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuItemConfig.kt +1 -3
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuModel.kt +180 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementOptions.kt +57 -6
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementRawUpdate.kt +3 -1
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementUpdate.kt +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuIconResolver.kt +4 -3
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuUpdateQueue.kt +23 -9
- package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackContainer.kt +103 -3
- package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackHost.kt +13 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackHostViewManager.kt +14 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreen.kt +4 -0
- package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreenFragment.kt +20 -1
- package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreenFragmentDelegate.kt +12 -0
- package/android/src/main/java/com/swmansion/rnscreens/tabs/appearance/TabsAppearanceCoordinator.kt +2 -4
- package/android/src/main/java/com/swmansion/rnscreens/tabs/container/MenuHelpers.kt +5 -23
- package/android/src/main/java/com/swmansion/rnscreens/tabs/container/TabsContainer.kt +46 -25
- package/android/src/main/java/com/swmansion/rnscreens/tabs/host/TabsHost.kt +10 -0
- package/android/src/main/java/com/swmansion/rnscreens/tabs/host/TabsHostA11yCoordinator.kt +23 -2
- package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreen.kt +17 -0
- package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenFragment.kt +3 -0
- package/android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt +7 -0
- package/android/src/main/java/com/swmansion/rnscreens/utils/StyleUtils.kt +22 -0
- package/android/src/main/res/base/layout/rns_collapsing_toolbar_large_available_space.xml +8 -0
- package/android/src/main/res/base/layout/rns_collapsing_toolbar_large_entire_space.xml +8 -0
- package/android/src/main/res/base/layout/rns_collapsing_toolbar_medium_available_space.xml +8 -0
- package/android/src/main/res/base/layout/rns_collapsing_toolbar_medium_entire_space.xml +8 -0
- package/cpp/legacy/RNSScreenRemovalListener.cpp +31 -1
- package/cpp/legacy/RNSScreenRemovalListener.h +18 -3
- package/ios/RNSEnums.h +2 -0
- package/ios/conversion/RNSConversions.h +7 -108
- package/ios/legacy/RNSFullWindowOverlay.mm +1 -1
- package/ios/legacy/RNSModalScreen.mm +1 -1
- package/ios/legacy/RNSScreen.mm +8 -19
- package/ios/legacy/RNSScreenStackHeaderConfig.mm +3 -1
- package/ios/legacy/RNSScreenStackHeaderSubview.mm +1 -1
- package/ios/legacy/RNSScreenWindowTraits.mm +5 -13
- package/ios/modals/form-sheet/RNSFormSheetConfigurationApplicator.h +1 -3
- package/ios/modals/form-sheet/RNSFormSheetConfigurationApplicator.mm +11 -22
- package/ios/modals/form-sheet/RNSFormSheetContentController.h +1 -1
- package/ios/modals/form-sheet/RNSFormSheetContentController.mm +7 -18
- package/ios/modals/form-sheet/RNSFormSheetHostComponentView.mm +4 -4
- package/ios/modals/form-sheet/RNSFormSheetHostShadowStateProxy.mm +2 -2
- package/ios/modals/form-sheet/RNSFormSheetUpdateFlags.h +3 -0
- package/ios/safe-area/RNSSafeAreaViewComponentView.mm +2 -2
- package/ios/{conversion → scroll-view-marker/conversion}/RNSConversions-ScrollViewMarker.mm +2 -3
- package/ios/split/RNSSplitAppearanceApplicator.h +4 -4
- package/ios/split/RNSSplitAppearanceApplicator.mm +53 -56
- package/ios/split/RNSSplitHostComponentEventEmitter.h +1 -0
- package/ios/split/RNSSplitHostComponentEventEmitter.mm +1 -1
- package/ios/split/RNSSplitHostComponentView.h +5 -15
- package/ios/split/RNSSplitHostComponentView.mm +55 -6
- package/ios/split/RNSSplitHostController.h +25 -6
- package/ios/split/RNSSplitHostController.mm +24 -92
- package/ios/split/RNSSplitHostProviders.h +67 -0
- package/ios/split/RNSSplitScreenComponentView.h +0 -39
- package/ios/split/RNSSplitScreenComponentView.mm +27 -10
- package/ios/split/RNSSplitScreenController.h +26 -3
- package/ios/split/RNSSplitScreenController.mm +13 -30
- package/ios/split/RNSSplitScreenShadowStateProxy.h +1 -40
- package/ios/split/RNSSplitScreenShadowStateProxy.mm +1 -26
- package/ios/split/conversion/RNSConversions-SplitView.h +56 -0
- package/ios/{conversion → split/conversion}/RNSConversions-SplitView.mm +4 -1
- package/ios/stack/conversion/RNSConversions-Stack.h +32 -0
- package/ios/stack/conversion/RNSConversions-Stack.mm +55 -0
- package/ios/stack/header/RNSStackHeaderAppearanceMapper.h +26 -0
- package/ios/stack/header/RNSStackHeaderAppearanceMapper.mm +88 -0
- package/ios/stack/header/RNSStackHeaderConfigComponentView.h +10 -1
- package/ios/stack/header/RNSStackHeaderConfigComponentView.mm +67 -35
- package/ios/stack/header/RNSStackHeaderConfigDataProviding.h +6 -0
- package/ios/stack/header/RNSStackHeaderConfigShadowStateProxy.mm +1 -1
- package/ios/stack/header/RNSStackHeaderIconResolver.mm +7 -0
- package/ios/stack/header/RNSStackHeaderItemComponentView.h +3 -1
- package/ios/stack/header/RNSStackHeaderItemComponentView.mm +15 -2
- package/ios/stack/header/RNSStackHeaderItemDataProviding.h +2 -0
- package/ios/stack/header/RNSStackHeaderItemShadowStateProxy.mm +1 -1
- package/ios/stack/header/RNSStackHeaderItemSpacerComponentView.mm +2 -1
- package/ios/stack/header/RNSStackHeaderMenuMapper.mm +4 -0
- package/ios/stack/host/RNSStackNavigationBar.h +25 -0
- package/ios/stack/host/RNSStackNavigationBar.mm +80 -0
- package/ios/stack/host/RNSStackNavigationBarCoordinator.h +8 -0
- package/ios/stack/host/RNSStackNavigationBarCoordinator.mm +11 -0
- package/ios/stack/host/RNSStackNavigationController.mm +13 -5
- package/ios/stack/host/RNSStackOperation.h +0 -2
- package/ios/stack/screen/RNSStackScreenComponentView.mm +9 -2
- package/ios/stack/screen/RNSStackScreenController.mm +12 -0
- package/ios/stack/screen/RNSStackScreenHeaderCoordinator.h +8 -0
- package/ios/stack/screen/RNSStackScreenHeaderCoordinator.mm +98 -16
- package/ios/tabs/RNSTabBarAppearanceCoordinator.mm +40 -9
- package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryComponentView.h +1 -1
- package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryComponentView.mm +1 -1
- package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryContentComponentView.mm +1 -1
- package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryEventEmitter.mm +1 -1
- package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryShadowStateProxy.mm +1 -1
- package/ios/tabs/conversion/RNSConversions-Tabs.h +78 -0
- package/ios/{conversion → tabs/conversion}/RNSConversions-Tabs.mm +8 -71
- package/ios/tabs/extensions/RNSTabsHostComponentView+RNSImageLoader.h +1 -1
- package/ios/tabs/host/RNSTabsHostComponentView.h +2 -0
- package/ios/tabs/host/RNSTabsHostComponentView.mm +10 -3
- package/ios/tabs/host/RNSTabsHostEventEmitter.mm +1 -1
- package/ios/tabs/screen/RNSTabsScreenComponentView.mm +20 -5
- package/ios/utils/RNSDefines.h +3 -0
- package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js +34 -19
- package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js.map +1 -1
- package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js +33 -1
- package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
- package/lib/commonjs/components/stack/host/StackHost.js +9 -6
- package/lib/commonjs/components/stack/host/StackHost.js.map +1 -1
- package/lib/commonjs/components/stack/index.js.map +1 -1
- package/lib/commonjs/components/tabs/host/TabsHost.ios.js +1 -0
- package/lib/commonjs/components/tabs/host/TabsHost.ios.js.map +1 -1
- package/lib/commonjs/fabric/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/stack/StackHostNativeComponent.js.map +1 -1
- package/lib/commonjs/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
- package/lib/module/components/stack/header/StackHeaderConfig.android.js +35 -20
- package/lib/module/components/stack/header/StackHeaderConfig.android.js.map +1 -1
- package/lib/module/components/stack/header/StackHeaderConfig.ios.js +34 -2
- package/lib/module/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
- package/lib/module/components/stack/host/StackHost.js +9 -6
- package/lib/module/components/stack/host/StackHost.js.map +1 -1
- package/lib/module/components/stack/index.js.map +1 -1
- package/lib/module/components/tabs/host/TabsHost.ios.js +1 -0
- package/lib/module/components/tabs/host/TabsHost.ios.js.map +1 -1
- package/lib/module/fabric/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
- package/lib/module/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
- package/lib/module/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
- package/lib/module/fabric/stack/StackHostNativeComponent.js.map +1 -1
- package/lib/module/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
- package/lib/typescript/components/modals/form-sheet/FormSheet.types.d.ts +1 -1
- package/lib/typescript/components/stack/header/StackHeaderConfig.android.d.ts.map +1 -1
- package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts +616 -21
- package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts.map +1 -1
- package/lib/typescript/components/stack/header/StackHeaderConfig.ios.d.ts.map +1 -1
- package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts +226 -2
- package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts.map +1 -1
- package/lib/typescript/components/stack/header/StackHeaderConfig.types.d.ts +2 -2
- package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts +2 -0
- package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts.map +1 -1
- package/lib/typescript/components/stack/host/StackHost.d.ts +1 -1
- package/lib/typescript/components/stack/host/StackHost.d.ts.map +1 -1
- package/lib/typescript/components/stack/host/StackHost.types.d.ts +24 -0
- package/lib/typescript/components/stack/host/StackHost.types.d.ts.map +1 -1
- package/lib/typescript/components/stack/index.d.ts +2 -2
- package/lib/typescript/components/stack/index.d.ts.map +1 -1
- package/lib/typescript/components/tabs/host/TabsHost.ios.d.ts.map +1 -1
- package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts +16 -0
- package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts.map +1 -1
- package/lib/typescript/components/tabs/host/TabsHost.types.d.ts +3 -0
- package/lib/typescript/components/tabs/host/TabsHost.types.d.ts.map +1 -1
- package/lib/typescript/fabric/stack/StackHeaderConfigAndroidNativeComponent.d.ts +52 -0
- package/lib/typescript/fabric/stack/StackHeaderConfigAndroidNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts +25 -1
- package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts +2 -0
- package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/stack/StackHostNativeComponent.d.ts +3 -1
- package/lib/typescript/fabric/stack/StackHostNativeComponent.d.ts.map +1 -1
- package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts +1 -0
- package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts.map +1 -1
- package/package.json +8 -7
- package/react-native.config.js +3 -0
- package/src/components/modals/form-sheet/FormSheet.types.ts +1 -1
- package/src/components/stack/header/StackHeaderConfig.android.tsx +48 -23
- package/src/components/stack/header/StackHeaderConfig.android.types.ts +637 -21
- package/src/components/stack/header/StackHeaderConfig.ios.tsx +45 -1
- package/src/components/stack/header/StackHeaderConfig.ios.types.ts +238 -2
- package/src/components/stack/header/StackHeaderConfig.types.ts +2 -2
- package/src/components/stack/header/ios/StackHeaderItem.ios.types.ts +2 -0
- package/src/components/stack/host/StackHost.tsx +4 -2
- package/src/components/stack/host/StackHost.types.ts +26 -0
- package/src/components/stack/index.ts +6 -1
- package/src/components/tabs/host/TabsHost.ios.tsx +1 -0
- package/src/components/tabs/host/TabsHost.ios.types.ts +16 -0
- package/src/components/tabs/host/TabsHost.types.ts +3 -0
- package/src/fabric/stack/StackHeaderConfigAndroidNativeComponent.ts +78 -0
- package/src/fabric/stack/StackHeaderConfigIOSNativeComponent.ts +32 -0
- package/src/fabric/stack/StackHeaderItemIOSNativeComponent.ts +2 -0
- package/src/fabric/stack/StackHostNativeComponent.ts +11 -2
- package/src/fabric/tabs/TabsHostIOSNativeComponent.ts +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingView.kt +0 -83
- package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingViewManager.kt +0 -89
- package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingViewPointerEvents.kt +0 -18
- package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuSelectionController.kt +0 -83
- package/ios/conversion/RNSConversions-Stack.h +0 -32
- package/ios/conversion/RNSConversions-Stack.mm +0 -41
- package/ios/conversion/always_false.h +0 -31
- /package/ios/{conversion → scroll-view-marker/conversion}/RNSConversions-ScrollViewMarker.h +0 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
package com.swmansion.rnscreens.stack.header.toolbar
|
|
2
|
+
|
|
3
|
+
import android.graphics.drawable.Drawable
|
|
4
|
+
import android.util.Log
|
|
5
|
+
import android.view.MenuItem
|
|
6
|
+
import com.google.android.material.appbar.MaterialToolbar
|
|
7
|
+
import com.swmansion.rnscreens.helpers.IconResolution
|
|
8
|
+
import com.swmansion.rnscreens.helpers.PropIconResolver
|
|
9
|
+
import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuConfig
|
|
10
|
+
import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuElementConfig
|
|
11
|
+
import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuItemIconSource
|
|
12
|
+
import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuModel
|
|
13
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarFieldUpdate
|
|
14
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementOptions
|
|
15
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementRawUpdate
|
|
16
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementUpdate
|
|
17
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuIconResolver
|
|
18
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuUpdateQueue
|
|
19
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.toOptions
|
|
20
|
+
import java.lang.ref.WeakReference
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Owns the toolbar menu: the parsed model and the runtime state accumulated on
|
|
24
|
+
* top of it (imperative element updates, group selections, resolved prop
|
|
25
|
+
* icons). The state is the source of truth and lives as long as this
|
|
26
|
+
* controller; an attached toolbar is merely a projection of it, rebuilt from
|
|
27
|
+
* scratch at every [attach]. Checked-state semantics run on the state, so
|
|
28
|
+
* updates behave identically whether a toolbar is attached or not.
|
|
29
|
+
*/
|
|
30
|
+
internal class StackHeaderToolbarMenuController(
|
|
31
|
+
private val iconResolver: StackHeaderToolbarMenuIconResolver,
|
|
32
|
+
) {
|
|
33
|
+
internal var delegate: WeakReference<StackHeaderToolbarMenuDelegate>? = null
|
|
34
|
+
|
|
35
|
+
// region Model
|
|
36
|
+
|
|
37
|
+
private var model = StackHeaderToolbarMenuModel.EMPTY
|
|
38
|
+
private var groupDividerEnabled = false
|
|
39
|
+
|
|
40
|
+
// Set by a menu change and cleared by [attach]: until then a live toolbar
|
|
41
|
+
// still shows the previous menu (with previous int ids), so in-place
|
|
42
|
+
// application is suspended and updates are recorded only.
|
|
43
|
+
private var modelDirty = false
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Replaces the menu configuration. Returns `true` when it actually
|
|
47
|
+
* changed; icon sources are part of the config, so an icon-source-only
|
|
48
|
+
* prop change counts. A real change validates the new menu, resets all
|
|
49
|
+
* runtime state, re-initializes group selections from
|
|
50
|
+
* `initialToggleState` and drops pending command batches. Application to
|
|
51
|
+
* the toolbar is the caller's responsibility (via [attach]).
|
|
52
|
+
*/
|
|
53
|
+
internal fun setMenu(menu: StackHeaderToolbarMenuConfig): Boolean {
|
|
54
|
+
if (menu == model.config) {
|
|
55
|
+
return false
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Throws on an invalid menu before any mutation, so the previous,
|
|
59
|
+
// valid model stays intact.
|
|
60
|
+
model = StackHeaderToolbarMenuModel.from(menu)
|
|
61
|
+
|
|
62
|
+
commandOverrides.clear()
|
|
63
|
+
propIcons.keys.retainAll(model.elementById.keys)
|
|
64
|
+
propIconResolvers.keys.retainAll(model.elementById.keys)
|
|
65
|
+
groupSelections =
|
|
66
|
+
model.groupMetadata.groupMemberItems
|
|
67
|
+
.mapValues { (_, members) ->
|
|
68
|
+
members.filterTo(mutableSetOf()) {
|
|
69
|
+
model.elementById
|
|
70
|
+
.getValue(it)
|
|
71
|
+
.item.initialToggleState
|
|
72
|
+
}
|
|
73
|
+
}.toMutableMap()
|
|
74
|
+
|
|
75
|
+
// Commands sent against the previous menu must not touch the new one,
|
|
76
|
+
// even those still waiting for an icon.
|
|
77
|
+
updateQueue.clearPending()
|
|
78
|
+
|
|
79
|
+
modelDirty = true
|
|
80
|
+
resolvePropIcons()
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Returns `true` when the value changed and the menu needs a rebuild. */
|
|
85
|
+
internal fun setGroupDividerEnabled(enabled: Boolean): Boolean {
|
|
86
|
+
if (groupDividerEnabled == enabled) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
groupDividerEnabled = enabled
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// endregion
|
|
94
|
+
|
|
95
|
+
// region State
|
|
96
|
+
|
|
97
|
+
// Resolved icons of prop-declared items, and the stateful per-item
|
|
98
|
+
// resolvers behind them; both pruned in [setMenu].
|
|
99
|
+
private val propIcons = mutableMapOf<String, Drawable>()
|
|
100
|
+
private val propIconResolvers = mutableMapOf<String, PropIconResolver>()
|
|
101
|
+
|
|
102
|
+
// Accumulated imperative element updates, merged field-wise (newer
|
|
103
|
+
// non-null wins). `checked` never reaches here — it travels on the update
|
|
104
|
+
// envelope and group selections are its single home.
|
|
105
|
+
private val commandOverrides = mutableMapOf<String, StackHeaderToolbarMenuElementOptions>()
|
|
106
|
+
|
|
107
|
+
// Fully materialized selection per group. Initialized from
|
|
108
|
+
// `initialToggleState` at [setMenu], mutated by taps and command `checked`.
|
|
109
|
+
private var groupSelections = mutableMapOf<String, MutableSet<String>>()
|
|
110
|
+
|
|
111
|
+
// endregion
|
|
112
|
+
|
|
113
|
+
// region Imperative updates
|
|
114
|
+
|
|
115
|
+
// Serial, batch-atomic command ingestion; owned here so a menu change can
|
|
116
|
+
// drop stale batches locally (see setMenu).
|
|
117
|
+
private val updateQueue =
|
|
118
|
+
StackHeaderToolbarMenuUpdateQueue(iconResolver) { applyElementUpdates(it) }
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Enqueues an `updateToolbarMenuElements` batch. Batches apply serially
|
|
122
|
+
* and atomically, once every icon in the batch has resolved — see
|
|
123
|
+
* [StackHeaderToolbarMenuUpdateQueue].
|
|
124
|
+
*/
|
|
125
|
+
internal fun enqueueElementUpdates(batch: List<StackHeaderToolbarMenuElementRawUpdate>) {
|
|
126
|
+
updateQueue.enqueue(batch)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Applies a fully resolved `updateToolbarMenuElements` batch: records it,
|
|
131
|
+
* runs checked-state transitions on the state, projects onto the live
|
|
132
|
+
* toolbar when one is attached, and emits one coalesced selection event per
|
|
133
|
+
* changed group — identically whether attached or not.
|
|
134
|
+
*/
|
|
135
|
+
private fun applyElementUpdates(updates: List<StackHeaderToolbarMenuElementUpdate>) {
|
|
136
|
+
val changedGroups = LinkedHashSet<String>()
|
|
137
|
+
val toolbar = liveToolbar()
|
|
138
|
+
|
|
139
|
+
for (update in updates) {
|
|
140
|
+
val id = update.id
|
|
141
|
+
if (id !in model.elementById) {
|
|
142
|
+
Log.w(TAG, "[RNScreens] Ignoring toolbar menu update for unknown item id '$id'.")
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
val options = applicableOptions(id, update.options)
|
|
147
|
+
|
|
148
|
+
if (!options.isEmpty) {
|
|
149
|
+
commandOverrides[id] = commandOverrides[id]?.mergedWith(options) ?: options
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
update.checked?.let { checked ->
|
|
153
|
+
applyCheckedInState(id, checked)?.let(changedGroups::add)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (toolbar != null && !options.isEmpty) {
|
|
157
|
+
StackHeaderToolbarMenuApplicator.updateToolbarMenuElement(
|
|
158
|
+
toolbar,
|
|
159
|
+
model.forwardIdMap,
|
|
160
|
+
id,
|
|
161
|
+
widenWithCoupledFields(id, options),
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (toolbar != null) {
|
|
167
|
+
changedGroups.forEach { applySelectionToToolbar(toolbar, it) }
|
|
168
|
+
}
|
|
169
|
+
changedGroups.forEach(::emitGroupSelection)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Drops options the target element cannot accept. They would otherwise be
|
|
174
|
+
* recorded in [commandOverrides], which outlive every rebuild, and be
|
|
175
|
+
* re-applied — and re-warned about — by each of them.
|
|
176
|
+
*/
|
|
177
|
+
private fun applicableOptions(
|
|
178
|
+
id: String,
|
|
179
|
+
options: StackHeaderToolbarMenuElementOptions,
|
|
180
|
+
): StackHeaderToolbarMenuElementOptions {
|
|
181
|
+
val targetsSubmenu = model.elementById[id] is StackHeaderToolbarMenuElementConfig.Submenu
|
|
182
|
+
if (options.menuTitle == null || targetsSubmenu) {
|
|
183
|
+
return options
|
|
184
|
+
}
|
|
185
|
+
Log.w(TAG, "[RNScreens] Ignoring menuTitle of '$id': the target is not a submenu.")
|
|
186
|
+
return options.copy(menuTitle = null)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// endregion
|
|
190
|
+
|
|
191
|
+
// region Prop icons
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Re-resolves every prop-declared icon source; called on a menu change —
|
|
195
|
+
* the only time sources can change. Results flow through [setItemIcon]:
|
|
196
|
+
* while the menu is dirty (until the next [attach]) they land in state
|
|
197
|
+
* only, later async ones also apply to the live toolbar in place.
|
|
198
|
+
*/
|
|
199
|
+
private fun resolvePropIcons() {
|
|
200
|
+
model.elementById.forEach { (id, element) ->
|
|
201
|
+
val source = element.item.iconSource
|
|
202
|
+
// An escaping exception would leave the menu dirty forever, with
|
|
203
|
+
// in-place application suspended until the next [attach].
|
|
204
|
+
try {
|
|
205
|
+
propIconResolvers
|
|
206
|
+
.getOrPut(id) {
|
|
207
|
+
PropIconResolver { name, uri, onComplete ->
|
|
208
|
+
iconResolver.resolve(
|
|
209
|
+
StackHeaderToolbarMenuItemIconSource(name, uri),
|
|
210
|
+
onComplete,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
}.resolve(source.drawableIconResourceName, source.imageIconUri) { result ->
|
|
214
|
+
when (result) {
|
|
215
|
+
IconResolution.Unchanged -> Unit
|
|
216
|
+
is IconResolution.Resolved -> setItemIcon(id, source, result.drawable)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
} catch (e: Exception) {
|
|
220
|
+
Log.e(TAG, "[RNScreens] Failed to resolve the icon of toolbar menu item '$id'.", e)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Sets or clears the resolved icon of a prop-declared item, in place —
|
|
227
|
+
* never rebuilds the menu and never touches command state. An icon set via
|
|
228
|
+
* a view command takes precedence until the next real menu change.
|
|
229
|
+
*/
|
|
230
|
+
private fun setItemIcon(
|
|
231
|
+
id: String,
|
|
232
|
+
source: StackHeaderToolbarMenuItemIconSource,
|
|
233
|
+
icon: Drawable?,
|
|
234
|
+
) {
|
|
235
|
+
// Pruning a resolver in [setMenu] does not cancel its in-flight load,
|
|
236
|
+
// and the orphan's latest-wins guard still matches the source it was
|
|
237
|
+
// asked for, so a result must be matched against the current model.
|
|
238
|
+
if (model.elementById[id]?.item?.iconSource != source) {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
val previous = if (icon != null) propIcons.put(id, icon) else propIcons.remove(id)
|
|
242
|
+
if (previous === icon || commandOverrides[id]?.icon != null) {
|
|
243
|
+
return
|
|
244
|
+
}
|
|
245
|
+
val toolbar = liveToolbar() ?: return
|
|
246
|
+
val effective = effectiveOptions(id)
|
|
247
|
+
StackHeaderToolbarMenuApplicator.updateToolbarMenuElement(
|
|
248
|
+
toolbar,
|
|
249
|
+
model.forwardIdMap,
|
|
250
|
+
id,
|
|
251
|
+
StackHeaderToolbarMenuElementOptions(
|
|
252
|
+
// Action-item classification runs when showAsAction is set and
|
|
253
|
+
// setting an icon alone does not re-run it, so it must
|
|
254
|
+
// accompany the icon.
|
|
255
|
+
showAsAction = effective.showAsAction,
|
|
256
|
+
icon = effective.icon,
|
|
257
|
+
iconTintColorNormal = effective.iconTintColorNormal,
|
|
258
|
+
iconTintColorPressed = effective.iconTintColorPressed,
|
|
259
|
+
iconTintColorFocused = effective.iconTintColorFocused,
|
|
260
|
+
iconTintColorDisabled = effective.iconTintColorDisabled,
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// endregion
|
|
266
|
+
|
|
267
|
+
// region Attachment & projection
|
|
268
|
+
|
|
269
|
+
private var attachedToolbar: WeakReference<MaterialToolbar>? = null
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Projects the menu (model ⊕ prop icons ⊕ command overrides ⊕ selections)
|
|
273
|
+
* onto [toolbar] and starts applying subsequent updates to it in place.
|
|
274
|
+
* Emits no events — the projection is semantically a no-change.
|
|
275
|
+
*/
|
|
276
|
+
internal fun attach(toolbar: MaterialToolbar) {
|
|
277
|
+
attachedToolbar = WeakReference(toolbar)
|
|
278
|
+
modelDirty = false
|
|
279
|
+
StackHeaderToolbarMenuApplicator.rebuildToolbarMenu(
|
|
280
|
+
toolbar,
|
|
281
|
+
model,
|
|
282
|
+
groupDividerEnabled,
|
|
283
|
+
optionsForItem = ::effectiveOptions,
|
|
284
|
+
onItemClicked = ::handleItemClick,
|
|
285
|
+
)
|
|
286
|
+
model.groupMetadata.groupMemberItems.keys
|
|
287
|
+
.forEach { applySelectionToToolbar(toolbar, it) }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
internal fun detach() {
|
|
291
|
+
attachedToolbar = null
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
internal fun tearDown() {
|
|
295
|
+
updateQueue.tearDown()
|
|
296
|
+
detach()
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private fun liveToolbar(): MaterialToolbar? = if (modelDirty) null else attachedToolbar?.get()
|
|
300
|
+
|
|
301
|
+
private fun applySelectionToToolbar(
|
|
302
|
+
toolbar: MaterialToolbar,
|
|
303
|
+
groupId: String,
|
|
304
|
+
) {
|
|
305
|
+
val selected = groupSelections[groupId].orEmpty()
|
|
306
|
+
|
|
307
|
+
// setChecked(false) on an item of an exclusive group checks it instead
|
|
308
|
+
// of clearing it, so only the selected member is written and the native
|
|
309
|
+
// sibling-uncheck does the rest. Such a group is never cleared.
|
|
310
|
+
if (model.groupMetadata.groupSingleSelection[groupId] == true) {
|
|
311
|
+
val selectedId = selected.singleOrNull() ?: return
|
|
312
|
+
menuItemFor(toolbar, selectedId)?.isChecked = true
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (memberId in model.groupMetadata.groupMemberItems[groupId].orEmpty()) {
|
|
317
|
+
val item = menuItemFor(toolbar, memberId) ?: continue
|
|
318
|
+
val shouldBeChecked = memberId in selected
|
|
319
|
+
if (item.isChecked != shouldBeChecked) {
|
|
320
|
+
item.isChecked = shouldBeChecked
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private fun menuItemFor(
|
|
326
|
+
toolbar: MaterialToolbar,
|
|
327
|
+
id: String,
|
|
328
|
+
): MenuItem? = model.forwardIdMap[id]?.let { toolbar.menu.findItem(it) }
|
|
329
|
+
|
|
330
|
+
// endregion
|
|
331
|
+
|
|
332
|
+
// region Checked-state semantics
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Mutates the selection of [itemId]'s group and returns the group id when
|
|
336
|
+
* it actually changed. [explicitCheckedValue] `null` means a user toggle.
|
|
337
|
+
*/
|
|
338
|
+
private fun applyCheckedInState(
|
|
339
|
+
itemId: String,
|
|
340
|
+
explicitCheckedValue: Boolean?,
|
|
341
|
+
): String? {
|
|
342
|
+
val groupId = model.groupMetadata.itemGroupMap[itemId] ?: return null
|
|
343
|
+
val singleSelection = model.groupMetadata.groupSingleSelection[groupId] ?: return null
|
|
344
|
+
val selection = groupSelections.getOrPut(groupId) { mutableSetOf() }
|
|
345
|
+
|
|
346
|
+
if (singleSelection) {
|
|
347
|
+
if (explicitCheckedValue == false) {
|
|
348
|
+
Log.w(
|
|
349
|
+
TAG,
|
|
350
|
+
"[RNScreens] Cannot uncheck item '$itemId' in single-selection group '$groupId'. " +
|
|
351
|
+
"Check a different item instead.",
|
|
352
|
+
)
|
|
353
|
+
return null
|
|
354
|
+
}
|
|
355
|
+
if (selection.singleOrNull() == itemId) {
|
|
356
|
+
return null
|
|
357
|
+
}
|
|
358
|
+
selection.clear()
|
|
359
|
+
selection.add(itemId)
|
|
360
|
+
return groupId
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
val newChecked = explicitCheckedValue ?: (itemId !in selection)
|
|
364
|
+
val changed = if (newChecked) selection.add(itemId) else selection.remove(itemId)
|
|
365
|
+
return groupId.takeIf { changed }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
private fun handleItemClick(id: String) {
|
|
369
|
+
if (!isCheckable(id)) {
|
|
370
|
+
delegate?.get()?.onMenuItemClicked(id)
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
val changedGroup = applyCheckedInState(id, explicitCheckedValue = null) ?: return
|
|
374
|
+
liveToolbar()?.let { applySelectionToToolbar(it, changedGroup) }
|
|
375
|
+
emitGroupSelection(changedGroup)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
private fun emitGroupSelection(groupId: String) {
|
|
379
|
+
val selection = groupSelections[groupId].orEmpty()
|
|
380
|
+
val selectedIds =
|
|
381
|
+
model.groupMetadata.groupMemberItems[groupId]
|
|
382
|
+
.orEmpty()
|
|
383
|
+
.filter { it in selection }
|
|
384
|
+
delegate?.get()?.onGroupSelectionChanged(groupId, selectedIds)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Validation guarantees only plain menu items carry a groupId, so
|
|
388
|
+
// checkability reduces to group membership.
|
|
389
|
+
private fun isCheckable(id: String): Boolean = model.elementById[id]?.item?.groupId != null
|
|
390
|
+
|
|
391
|
+
// endregion
|
|
392
|
+
|
|
393
|
+
// region Effective values
|
|
394
|
+
|
|
395
|
+
private fun effectiveOptions(id: String): StackHeaderToolbarMenuElementOptions {
|
|
396
|
+
val base = model.elementById.getValue(id).toOptions()
|
|
397
|
+
val withPropIcon =
|
|
398
|
+
propIcons[id]?.let { base.copy(icon = StackHeaderToolbarFieldUpdate.Set(it)) } ?: base
|
|
399
|
+
return commandOverrides[id]?.let(withPropIcon::mergedWith) ?: withPropIcon
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* The live item already reflects effective state, so applying the delta
|
|
404
|
+
* keeps it there. Three fields need widening: tint colors resolve as one
|
|
405
|
+
* complete ColorStateList, so touching any tint slot (or the icon, whose
|
|
406
|
+
* application re-tints) requires all four; `showAsAction` must accompany a
|
|
407
|
+
* new icon, because action-item classification runs when it is set and
|
|
408
|
+
* setting an icon alone does not re-run it; and a submenu header must be
|
|
409
|
+
* re-asserted on every title change, because `MenuItem.setTitle`
|
|
410
|
+
* propagates into it and would leave it following the title until the
|
|
411
|
+
* next rebuild projected the effective `menuTitle` back.
|
|
412
|
+
*/
|
|
413
|
+
private fun widenWithCoupledFields(
|
|
414
|
+
id: String,
|
|
415
|
+
delta: StackHeaderToolbarMenuElementOptions,
|
|
416
|
+
): StackHeaderToolbarMenuElementOptions {
|
|
417
|
+
val effective = effectiveOptions(id)
|
|
418
|
+
var resolved = delta
|
|
419
|
+
|
|
420
|
+
if (delta.icon != null || delta.requiresIconTintColorUpdate) {
|
|
421
|
+
resolved =
|
|
422
|
+
resolved.copy(
|
|
423
|
+
iconTintColorNormal = effective.iconTintColorNormal,
|
|
424
|
+
iconTintColorPressed = effective.iconTintColorPressed,
|
|
425
|
+
iconTintColorFocused = effective.iconTintColorFocused,
|
|
426
|
+
iconTintColorDisabled = effective.iconTintColorDisabled,
|
|
427
|
+
)
|
|
428
|
+
if (delta.icon != null) {
|
|
429
|
+
resolved = resolved.copy(showAsAction = effective.showAsAction)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (delta.title != null &&
|
|
434
|
+
delta.menuTitle == null &&
|
|
435
|
+
model.elementById[id] is StackHeaderToolbarMenuElementConfig.Submenu
|
|
436
|
+
) {
|
|
437
|
+
resolved = resolved.copy(menuTitle = effective.menuTitle)
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return resolved
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// endregion
|
|
444
|
+
|
|
445
|
+
companion object {
|
|
446
|
+
private const val TAG = "StackHeaderToolbarMenuController"
|
|
447
|
+
}
|
|
448
|
+
}
|
|
@@ -21,6 +21,7 @@ import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenu
|
|
|
21
21
|
import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuItemType
|
|
22
22
|
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarFieldUpdate
|
|
23
23
|
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementOptions
|
|
24
|
+
import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementRawUpdate
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Parses React toolbar-menu props (Dynamic / ReadableMap) into the native menu
|
|
@@ -32,23 +33,37 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
32
33
|
internal fun parseMenu(
|
|
33
34
|
context: Context,
|
|
34
35
|
value: Dynamic,
|
|
35
|
-
):
|
|
36
|
-
if (value.isNull) return
|
|
36
|
+
): StackHeaderToolbarMenuConfig {
|
|
37
|
+
if (value.isNull) return StackHeaderToolbarMenuConfig(emptyList(), emptyList())
|
|
37
38
|
val map =
|
|
38
39
|
value.asMapOrNull()
|
|
39
40
|
?: throw JSApplicationIllegalArgumentException(
|
|
40
41
|
"[RNScreens] toolbarMenu must be an object.",
|
|
41
42
|
)
|
|
42
|
-
|
|
43
|
-
val config = StackHeaderToolbarMenuConfig(parseGroups(map), parseChildren(context, map, iconSources))
|
|
44
|
-
return Pair(config, iconSources)
|
|
43
|
+
return StackHeaderToolbarMenuConfig(parseGroups(map), parseChildren(context, map))
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
// endregion
|
|
48
47
|
|
|
49
48
|
// region Menu element command parsing
|
|
50
49
|
|
|
51
|
-
internal fun
|
|
50
|
+
internal fun parseElementRawUpdate(
|
|
51
|
+
context: Context,
|
|
52
|
+
id: String,
|
|
53
|
+
map: ReadableMap,
|
|
54
|
+
): StackHeaderToolbarMenuElementRawUpdate =
|
|
55
|
+
StackHeaderToolbarMenuElementRawUpdate(
|
|
56
|
+
id = id,
|
|
57
|
+
options = parseMenuElementOptions(context, map),
|
|
58
|
+
iconSource = parseCommandIconSource(map),
|
|
59
|
+
checked =
|
|
60
|
+
map.readNullableBooleanUpdate(
|
|
61
|
+
"checked",
|
|
62
|
+
StackHeaderToolbarMenuItemDefaults.INITIAL_TOGGLE_STATE,
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
private fun parseMenuElementOptions(
|
|
52
67
|
context: Context,
|
|
53
68
|
map: ReadableMap,
|
|
54
69
|
): StackHeaderToolbarMenuElementOptions =
|
|
@@ -64,20 +79,15 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
64
79
|
"showAsAction",
|
|
65
80
|
StackHeaderToolbarMenuItemDefaults.SHOW_AS_ACTION,
|
|
66
81
|
),
|
|
67
|
-
icon = null,
|
|
68
82
|
iconTintColorNormal = map.readNullableColorUpdate(context, "iconTintColorNormal"),
|
|
69
83
|
iconTintColorPressed = map.readNullableColorUpdate(context, "iconTintColorPressed"),
|
|
70
84
|
iconTintColorFocused = map.readNullableColorUpdate(context, "iconTintColorFocused"),
|
|
71
85
|
iconTintColorDisabled = map.readNullableColorUpdate(context, "iconTintColorDisabled"),
|
|
72
|
-
checked =
|
|
73
|
-
map.readNullableBooleanUpdate(
|
|
74
|
-
"checked",
|
|
75
|
-
StackHeaderToolbarMenuItemDefaults.INITIAL_TOGGLE_STATE,
|
|
76
|
-
),
|
|
77
86
|
menuTitle = map.readNullableStringUpdate("menuTitle"),
|
|
78
87
|
)
|
|
79
88
|
|
|
80
|
-
|
|
89
|
+
// Command icons are optional deltas: null when neither key is present.
|
|
90
|
+
private fun parseCommandIconSource(map: ReadableMap): StackHeaderToolbarMenuItemIconSource? {
|
|
81
91
|
if (!map.hasKey("drawableIconResourceName") && !map.hasKey("imageIconResource")) {
|
|
82
92
|
return null
|
|
83
93
|
}
|
|
@@ -112,7 +122,6 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
112
122
|
private fun parseChildren(
|
|
113
123
|
context: Context,
|
|
114
124
|
map: ReadableMap,
|
|
115
|
-
iconSources: MutableMap<String, StackHeaderToolbarMenuItemIconSource>,
|
|
116
125
|
): List<StackHeaderToolbarMenuElementConfig> {
|
|
117
126
|
val array = map.getArray("children") ?: return emptyList()
|
|
118
127
|
return (0 until array.size()).map { i ->
|
|
@@ -120,23 +129,21 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
120
129
|
requireNotNull(array.getMap(i)) {
|
|
121
130
|
"[RNScreens] Menu children array must contain valid menu element specification objects."
|
|
122
131
|
}
|
|
123
|
-
parseElement(context, child
|
|
132
|
+
parseElement(context, child)
|
|
124
133
|
}
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
private fun parseElement(
|
|
128
137
|
context: Context,
|
|
129
138
|
map: ReadableMap,
|
|
130
|
-
iconSources: MutableMap<String, StackHeaderToolbarMenuItemIconSource>,
|
|
131
139
|
): StackHeaderToolbarMenuElementConfig {
|
|
132
140
|
val item = parseItemConfig(context, map)
|
|
133
|
-
iconSources[item.id] = parseItemIconSource(map)
|
|
134
141
|
return when (val type = map.readOptionalString("type")) {
|
|
135
142
|
"menuItem" -> StackHeaderToolbarMenuElementConfig.MenuItem(item = item)
|
|
136
143
|
"menu" ->
|
|
137
144
|
StackHeaderToolbarMenuElementConfig.Submenu(
|
|
138
145
|
item = item,
|
|
139
|
-
menu = StackHeaderToolbarMenuConfig(parseGroups(map), parseChildren(context, map
|
|
146
|
+
menu = StackHeaderToolbarMenuConfig(parseGroups(map), parseChildren(context, map)),
|
|
140
147
|
menuTitle = map.readOptionalString("menuTitle"),
|
|
141
148
|
)
|
|
142
149
|
|
|
@@ -147,7 +154,8 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
147
154
|
}
|
|
148
155
|
}
|
|
149
156
|
|
|
150
|
-
|
|
157
|
+
// Prop icons are absolute per-item state: absent keys parse to defaults.
|
|
158
|
+
private fun parsePropIconSource(map: ReadableMap): StackHeaderToolbarMenuItemIconSource =
|
|
151
159
|
StackHeaderToolbarMenuItemIconSource(
|
|
152
160
|
drawableIconResourceName =
|
|
153
161
|
map.getString("drawableIconResourceName")
|
|
@@ -172,7 +180,6 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
172
180
|
hidden = map.readBoolean("hidden", StackHeaderToolbarMenuItemDefaults.HIDDEN),
|
|
173
181
|
disabled = map.readBoolean("disabled", StackHeaderToolbarMenuItemDefaults.DISABLED),
|
|
174
182
|
showAsAction = map.readShowAsActionEnum("showAsAction", StackHeaderToolbarMenuItemDefaults.SHOW_AS_ACTION),
|
|
175
|
-
icon = null,
|
|
176
183
|
iconTintColorNormal =
|
|
177
184
|
map.readColor(
|
|
178
185
|
context,
|
|
@@ -197,6 +204,7 @@ internal object StackHeaderToolbarMenuMapper {
|
|
|
197
204
|
"iconTintColorDisabled",
|
|
198
205
|
StackHeaderToolbarMenuItemDefaults.ICON_TINT_COLOR_DISABLED,
|
|
199
206
|
),
|
|
207
|
+
iconSource = parsePropIconSource(map),
|
|
200
208
|
groupId = map.readOptionalString("groupId"),
|
|
201
209
|
itemType = map.readItemTypeEnum("itemType", StackHeaderToolbarMenuItemDefaults.ITEM_TYPE),
|
|
202
210
|
initialToggleState =
|
|
@@ -1,60 +1,8 @@
|
|
|
1
1
|
package com.swmansion.rnscreens.stack.header.toolbar.model
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
// Immutable tree of data classes. Updates walk the tree recursively and use
|
|
6
|
-
// reference comparison (!==) to detect actual changes — only the path from
|
|
7
|
-
// the changed leaf to the root is copied; unchanged subtrees keep their
|
|
8
|
-
// original instances.
|
|
3
|
+
// Immutable, Drawable-free tree of data classes parsed from the `toolbarMenu`
|
|
4
|
+
// prop, so structural equality is sound (used to detect real prop changes).
|
|
9
5
|
internal data class StackHeaderToolbarMenuConfig(
|
|
10
6
|
val groups: List<StackHeaderToolbarMenuGroupConfig>,
|
|
11
7
|
val children: List<StackHeaderToolbarMenuElementConfig>,
|
|
12
|
-
)
|
|
13
|
-
internal fun updateItemIcon(
|
|
14
|
-
id: String,
|
|
15
|
-
icon: Drawable?,
|
|
16
|
-
): StackHeaderToolbarMenuConfig {
|
|
17
|
-
val updated = updateChildrenIcon(children, id, icon)
|
|
18
|
-
return if (updated !== children) copy(children = updated) else this
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
private fun updateChildrenIcon(
|
|
22
|
-
children: List<StackHeaderToolbarMenuElementConfig>,
|
|
23
|
-
id: String,
|
|
24
|
-
icon: Drawable?,
|
|
25
|
-
): List<StackHeaderToolbarMenuElementConfig> {
|
|
26
|
-
for ((index, element) in children.withIndex()) {
|
|
27
|
-
val updated = updateElementIcon(element, id, icon)
|
|
28
|
-
if (updated !== element) {
|
|
29
|
-
return children.toMutableList().apply { set(index, updated) }
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
return children
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
private fun updateElementIcon(
|
|
36
|
-
element: StackHeaderToolbarMenuElementConfig,
|
|
37
|
-
id: String,
|
|
38
|
-
icon: Drawable?,
|
|
39
|
-
): StackHeaderToolbarMenuElementConfig {
|
|
40
|
-
if (element.item.id == id) {
|
|
41
|
-
if (icon !== element.item.icon) {
|
|
42
|
-
val newItem = element.item.copy(icon = icon)
|
|
43
|
-
return when (element) {
|
|
44
|
-
is StackHeaderToolbarMenuElementConfig.MenuItem -> element.copy(item = newItem)
|
|
45
|
-
is StackHeaderToolbarMenuElementConfig.Submenu -> element.copy(item = newItem)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
return element
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
|
|
52
|
-
val updatedChildren = updateChildrenIcon(element.menu.children, id, icon)
|
|
53
|
-
if (updatedChildren !== element.menu.children) {
|
|
54
|
-
return element.copy(menu = element.menu.copy(children = updatedChildren))
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return element
|
|
59
|
-
}
|
|
60
|
-
}
|
|
8
|
+
)
|
|
@@ -8,13 +8,4 @@ internal data class StackHeaderToolbarMenuGroupMetadata(
|
|
|
8
8
|
val itemGroupMap: Map<String, String>,
|
|
9
9
|
val groupSingleSelection: Map<String, Boolean>,
|
|
10
10
|
val groupMemberItems: Map<String, List<String>>,
|
|
11
|
-
)
|
|
12
|
-
companion object {
|
|
13
|
-
val EMPTY =
|
|
14
|
-
StackHeaderToolbarMenuGroupMetadata(
|
|
15
|
-
emptyMap(),
|
|
16
|
-
emptyMap(),
|
|
17
|
-
emptyMap(),
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
}
|
|
11
|
+
)
|