From 357d048ae82820c92ef9f8cdcdec6092a173eebe Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Mon, 20 Jan 2025 22:40:22 +0530 Subject: [PATCH 01/15] feat(iOS): add support to show/hide iOS 18+ elevated tab bar --- apidoc/Titanium/UI/TabGroup.yml | 19 +++++++++++++++++++ iphone/Classes/TiUITabGroup.h | 1 + iphone/Classes/TiUITabGroup.m | 20 ++++++++------------ iphone/Classes/TiUITabGroupProxy.m | 10 ++++++++++ 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/apidoc/Titanium/UI/TabGroup.yml b/apidoc/Titanium/UI/TabGroup.yml index 492f203906d..63eba7880c9 100644 --- a/apidoc/Titanium/UI/TabGroup.yml +++ b/apidoc/Titanium/UI/TabGroup.yml @@ -246,6 +246,22 @@ methods: since: "10.0.0" removed: "10.0.0" notes: Use the property instead. + + - name: showTabBar + summary: Shows the tab bar with animation. + description: | + This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). + To show tab bar without animation, set [tabBarVisible](Titanium.UI.TabGroup.tabBarVisible) property to `false`. + platforms: [iphone, ipad, macos] + since: "12.7.0" + + - name: hideTabBar + summary: Hides the tab bar with animation. + description: | + This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). + To hide tab bar without animation, set [tabBarVisible](Titanium.UI.TabGroup.tabBarVisible) property to `true`. + platforms: [iphone, ipad, macos] + since: "12.7.0" properties: - name: activeTab @@ -614,7 +630,10 @@ properties: - name: tabBarVisible summary: Programmatically shows / hides the bottom tab bar of the tab group. + description: | + This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). type: Boolean + default: true platforms: [android, iphone, ipad, macos] since: "12.1.0" diff --git a/iphone/Classes/TiUITabGroup.h b/iphone/Classes/TiUITabGroup.h index 8790e19a751..4bddf0ff948 100644 --- a/iphone/Classes/TiUITabGroup.h +++ b/iphone/Classes/TiUITabGroup.h @@ -26,6 +26,7 @@ - (void)open:(id)args; - (void)close:(id)args; +- (void)hideTabBar:(BOOL)hidden animated:(BOOL)animated; - (NSDictionary *)focusEvent; diff --git a/iphone/Classes/TiUITabGroup.m b/iphone/Classes/TiUITabGroup.m index 143ba28a2a5..ad8c4b07177 100644 --- a/iphone/Classes/TiUITabGroup.m +++ b/iphone/Classes/TiUITabGroup.m @@ -370,20 +370,16 @@ - (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizin - (void)setTabBarVisible_:(id)value { - BOOL visible = [TiUtils boolValue:value]; + [self hideTabBar:[TiUtils boolValue:value] animated:NO]; +} - if ([self tabBarIsVisible] == visible) { - return; +- (void)hideTabBar:(BOOL)hidden animated:(BOOL)animated +{ + if (@available(iOS 18.0, *)) { + [self.tabController setTabBarHidden:hidden animated:animated]; + } else { + self.tabController.tabBar.hidden = hidden; } - - CGRect frame = self.tabController.tabBar.frame; - CGFloat height = frame.size.height; - - [UIView animateWithDuration:0.3 - animations:^{ - self.tabController.tabBar.frame = CGRectOffset(frame, 0, visible ? -height : height); - } - completion:nil]; } - (BOOL)tabBarIsVisible diff --git a/iphone/Classes/TiUITabGroupProxy.m b/iphone/Classes/TiUITabGroupProxy.m index b3a0ba89b08..e3d3412dcbb 100644 --- a/iphone/Classes/TiUITabGroupProxy.m +++ b/iphone/Classes/TiUITabGroupProxy.m @@ -141,6 +141,16 @@ - (void)_resetTabArray:(NSArray *)newTabOrder tabs = [newTabOrder mutableCopy]; } +- (void)hideTabBar:(id)args +{ + [(TiUITabGroup *)[self view] hideTabBar:YES animated:YES]; +} + +- (void)showTabBar:(id)args +{ + [(TiUITabGroup *)[self view] hideTabBar:NO animated:YES]; +} + #pragma mark Window Management - (void)windowWillOpen From 69c87c099f3ee4bd35c50e0d912d3f8548bfdfd0 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Mon, 20 Jan 2025 22:53:40 +0530 Subject: [PATCH 02/15] chore: removed methods which actually do not show/hide tab-bar --- apidoc/Titanium/UI/NavigationWindow.yml | 2 +- apidoc/Titanium/UI/Window.yml | 9 --------- apidoc/Titanium/UI/iOS/NavigationWindow.yml | 2 +- .../TitaniumKit/Sources/Modules/TiUIWindowProxy.m | 10 ---------- 4 files changed, 2 insertions(+), 21 deletions(-) diff --git a/apidoc/Titanium/UI/NavigationWindow.yml b/apidoc/Titanium/UI/NavigationWindow.yml index 83e9098e85d..1a0475e94b7 100644 --- a/apidoc/Titanium/UI/NavigationWindow.yml +++ b/apidoc/Titanium/UI/NavigationWindow.yml @@ -17,7 +17,7 @@ platforms: [android, iphone, ipad, macos] extends: Titanium.UI.Window since: "8.0.0" excludes: - methods: [removeAllChildren, setToolbar, hideTabBar] + methods: [removeAllChildren, setToolbar] properties: [navBarHidden, tabBarHidden, navTintColor, translucent, toolbar, barColor, barImage, leftNavButton, rightNavButton, title, titleControl, titlePrompt, titleImage, titleid, titlepromptid, url, backButtonTitle, diff --git a/apidoc/Titanium/UI/Window.yml b/apidoc/Titanium/UI/Window.yml index b17a8f5a30b..5c7a23ed764 100644 --- a/apidoc/Titanium/UI/Window.yml +++ b/apidoc/Titanium/UI/Window.yml @@ -535,15 +535,6 @@ methods: default: "{ animated: true }" platforms: [iphone, ipad, macos] - - name: hideTabBar - summary: Hides the tab bar. Must be called before opening the window. - description: | - To hide the tab bar when opening a window as a child of a tab, call - `hideTabBar` or set `tabBarHidden` to `true` **before** opening the window. - - If the window is not a child of a tab, this method has no effect. - platforms: [iphone, ipad, macos] - - name: open summary: Opens the window. parameters: diff --git a/apidoc/Titanium/UI/iOS/NavigationWindow.yml b/apidoc/Titanium/UI/iOS/NavigationWindow.yml index 2a14e9c500f..475680a947b 100644 --- a/apidoc/Titanium/UI/iOS/NavigationWindow.yml +++ b/apidoc/Titanium/UI/iOS/NavigationWindow.yml @@ -21,7 +21,7 @@ deprecated: notes: Use [Titanium.UI.NavigationWindow](Titanium.UI.NavigationWindow) instead. removed: "10.0.0" excludes: - methods: [removeAllChildren, setToolbar, hideTabBar] + methods: [removeAllChildren, setToolbar] properties: [navBarHidden, tabBarHidden, navTintColor, translucent, toolbar, barColor, barImage, leftNavButton, rightNavButton, title, titleControl, titlePrompt, titleImage, titleid, titlepromptid, url, backButtonTitle, diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m b/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m index 04af732df17..41c5dc97aae 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m @@ -694,16 +694,6 @@ - (void)setTabBarHidden:(id)value NO); } -- (void)hideTabBar:(id)value -{ - [self setTabBarHidden:[NSNumber numberWithBool:YES]]; -} - -- (void)showTabBar:(id)value -{ - [self setTabBarHidden:[NSNumber numberWithBool:NO]]; -} - - (void)refreshBackButton { ENSURE_UI_THREAD_0_ARGS; From e28cc659c737a57362be2d8cb1e015e0d6ebc301 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Wed, 19 Mar 2025 02:48:55 +0530 Subject: [PATCH 03/15] feat(android): add new methods and fix existing properties handling use showTabBar/hideTabBar for animation, use tabBarVisible without animation --- .../ti/modules/titanium/ui/TabGroupProxy.java | 72 +++++++++++++------ .../widget/tabgroup/TiUIAbstractTabGroup.java | 59 +++++++++++++++ .../TiUIBottomNavigationTabGroup.java | 55 ++++++-------- .../tabgroup/TiUITabLayoutTabGroup.java | 30 ++++++-- .../java/org/appcelerator/titanium/TiC.java | 1 + 5 files changed, 155 insertions(+), 62 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index 0c76925eb2e..ab6ee198a83 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -35,7 +35,6 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; -import java.util.HashMap; import ti.modules.titanium.ui.android.AndroidModule; import ti.modules.titanium.ui.widget.tabgroup.TiUIAbstractTabGroup; @@ -69,6 +68,8 @@ public class TabGroupProxy extends TiWindowProxy implements TiActivityWindow private Object selectedTab; // NOTE: Can be TabProxy or Number private String tabGroupTitle = null; private boolean autoTabTitle = false; + private boolean isTabBarVisible = true; + private boolean isTabNavigationDisabled = false; public TabGroupProxy() { @@ -83,24 +84,15 @@ public int getTabIndex(TabProxy tabProxy) return tabs.indexOf(tabProxy); } - @Kroll.method - public void disableTabNavigation(Object params) + @Kroll.setProperty + public void disableTabNavigation(boolean disable) { - if (params instanceof Boolean) { - TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view; - if (tabGroup != null) { - tabGroup.disableTabNavigation(TiConvert.toBoolean(params, false)); - } - } else if (params instanceof HashMap) { - KrollDict options = new KrollDict((HashMap) params); - TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view; - if (tabGroup != null) { - if (options.getBoolean(TiC.PROPERTY_ANIMATED)) { - setTabBarVisible(options.getBoolean(TiC.PROPERTY_ENABLED)); - } else { - tabGroup.disableTabNavigation(options.getBoolean(TiC.PROPERTY_ENABLED)); - } - } + isTabNavigationDisabled = disable; + + if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) { + bottomTabGroup.disableTabNavigation(isTabNavigationDisabled); + } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { + tabGroupDefault.disableTabNavigation(isTabNavigationDisabled); } } @@ -126,10 +118,33 @@ public void addTab(TabProxy tab) @Kroll.setProperty public void setTabBarVisible(boolean visible) { - TiUIBottomNavigationTabGroup tabGroup = (TiUIBottomNavigationTabGroup) view; + isTabBarVisible = visible; - if (tabGroup != null) { - tabGroup.setTabBarVisible(visible); + if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) { + bottomTabGroup.setTabGroupVisibility(visible); + } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { + tabGroupDefault.setTabGroupVisibility(visible); + } + } + + @Kroll.method + public void showTabBar() + { + showHideTabBar(true); + } + + @Kroll.method + public void hideTabBar() + { + showHideTabBar(false); + } + + private void showHideTabBar(boolean visible) + { + if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) { + bottomTabGroup.showHideTabBar(visible); + } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { + tabGroupDefault.showHideTabBar(visible); } } @@ -220,8 +235,7 @@ public void setTabs(Object obj) } tabs.clear(); - if (obj instanceof Object[]) { - Object[] objArray = (Object[]) obj; + if (obj instanceof Object[] objArray) { for (Object tabProxy : objArray) { if (tabProxy instanceof TabProxy) { addTab((TabProxy) tabProxy); @@ -279,6 +293,14 @@ public void handleCreationDict(KrollDict options) if (options.containsKeyAndNotNull(TiC.PROPERTY_ACTIVE_TAB)) { setActiveTab(options.get(TiC.PROPERTY_ACTIVE_TAB)); } + if (options.containsKeyAndNotNull(TiC.PROPERTY_TAB_BAR_VISIBLE)) { + isTabBarVisible = options.optBoolean(TiC.PROPERTY_TAB_BAR_VISIBLE, isTabBarVisible); + } + if (options.containsKeyAndNotNull(TiC.PROPERTY_DISABLE_TAB_NAVIGATION)) { + isTabNavigationDisabled = options.optBoolean( + TiC.PROPERTY_DISABLE_TAB_NAVIGATION, isTabNavigationDisabled + ); + } } @Kroll.getProperty @@ -461,6 +483,10 @@ protected void handlePostOpen() // Prevent any duplicate events from firing by marking // this group has having focus. isFocused = true; + + // Update UI if these properties are set before the native view is created. + disableTabNavigation(isTabNavigationDisabled); + setTabBarVisible(isTabBarVisible); } @Override diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java index 57d27a4ebab..383ae31308f 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java @@ -6,6 +6,8 @@ */ package ti.modules.titanium.ui.widget.tabgroup; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -51,6 +53,8 @@ import ti.modules.titanium.ui.TabGroupProxy; import ti.modules.titanium.ui.TabProxy; import com.google.android.material.R; +import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.android.material.tabs.TabLayout; /** * Abstract class representing Tab Navigation in Titanium. Abstract methods in it @@ -650,6 +654,61 @@ public static ColorStateList createRippleColorStateListFrom(@ColorInt int colorI return new ColorStateList(rippleStates, rippleColors); } + public void setTabGroupVisibilityWithAnimation(View view, boolean visible) + { + if (this.proxy == null || this.proxy.peekView() == null) { + return; + } + + int translationY = view.getHeight(); + if (view instanceof TabLayout) { + translationY = -translationY; + } + + view.animate() + .translationY(visible ? 0 : translationY) + .setDuration(250) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) + { + if (visible) { + view.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationEnd(Animator animation) + { + if (!visible) { + view.setVisibility(View.GONE); + } + } + }); + + updateInsets(view); + } + + public void setTabGroupVisibility(View view, boolean visible) + { + if (this.proxy == null || this.proxy.peekView() == null) { + return; + } + + view.setVisibility(visible ? View.VISIBLE : View.GONE); + view.requestLayout(); + updateInsets(view); + } + + private void updateInsets(View view) + { + if (view instanceof BottomNavigationView) { + this.insetsProvider.setBottomBasedOn(view); + } else { + this.insetsProvider.setTopBasedOn(view); + } + } + /** * Implementation of the FragmentPagerAdapter */ diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index 1ead9f66615..5b3c93abf70 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -180,14 +180,6 @@ public void onLayoutChange( // Set the ViewPager as a native view. setNativeView(this.tabGroupViewPager); - if (!TiConvert.toBoolean(this.proxy.getProperty(TiC.PROPERTY_TAB_BAR_VISIBLE), true)) { - TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); - params.autoFillsWidth = true; - params.optionBottom = new TiDimension(0, TiDimension.TYPE_BOTTOM); - this.tabGroupViewPager.setLayoutParams(params); - // FIXME change "500" to a calculate value https://github.com/tidev/titanium-sdk/issues/13900 - this.mBottomNavigationView.setTranslationY(500); - } } /** @@ -198,23 +190,6 @@ public void onLayoutChange( public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); - - // Resize the view pager (the tab's content) to compensate for shown/hidden tab bar. - // Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case. - ViewParent viewParent = this.tabGroupViewPager.getParent(); - if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) { - TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); - params.autoFillsWidth = true; - params.optionBottom = new TiDimension(disable ? 0 : mBottomNavigationHeightValue, TiDimension.TYPE_BOTTOM); - this.tabGroupViewPager.setLayoutParams(params); - } - - // Show/hide the tab bar. - this.mBottomNavigationView.setVisibility(disable ? View.GONE : View.VISIBLE); - this.mBottomNavigationView.requestLayout(); - - // Update top inset. (Will remove bottom inset if tab bar is "gone".) - this.insetsProvider.setBottomBasedOn(this.mBottomNavigationView); } @Override @@ -378,7 +353,7 @@ public void updateTabTitle(int index) this.mBottomNavigationView.getMenu().getItem(index).setTitle(title); } - public void setTabBarVisible(boolean visible) + public void showHideTabBar(boolean visible) { ViewParent viewParent = this.tabGroupViewPager.getParent(); @@ -389,22 +364,30 @@ public void setTabBarVisible(boolean visible) params.autoFillsWidth = true; params.optionBottom = new TiDimension(!visible ? 0 : mBottomNavigationHeightValue, TiDimension.TYPE_BOTTOM); - // make it a bit slower when moving up again so it won't show the background - int duration = !visible ? 200 : 400; LayoutTransition lt = new LayoutTransition(); lt.enableTransitionType(LayoutTransition.CHANGING); - lt.setDuration(duration); + lt.setDuration(250); this.tabGroupViewPager.setLayoutTransition(lt); this.tabGroupViewPager.setLayoutParams(params); } - if (visible) { - this.mBottomNavigationView.animate().translationY(0f).setDuration(200); - } else { - this.mBottomNavigationView.animate().translationY(mBottomNavigationView.getHeight()).setDuration(200); + super.setTabGroupVisibilityWithAnimation(mBottomNavigationView, visible); + } + + public void setTabGroupVisibility(boolean visible) + { + ViewParent viewParent = this.tabGroupViewPager.getParent(); + + // Resize the view pager (the tab's content) to compensate for shown/hidden tab bar. + // Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case. + if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) { + TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); + params.autoFillsWidth = true; + params.optionBottom = new TiDimension(!visible ? 0 : mBottomNavigationHeightValue, TiDimension.TYPE_BOTTOM); + this.tabGroupViewPager.setLayoutParams(params); } - this.insetsProvider.setBottomBasedOn(this.mBottomNavigationView); + super.setTabGroupVisibility(mBottomNavigationView, visible); } @SuppressLint("RestrictedApi") @@ -523,6 +506,10 @@ public String getTabTitle(int index) @Override public boolean onMenuItemClick(MenuItem item) { + if (tabsDisabled) { + return true; + } + // The controller has changed its selected item. int index = this.mMenuItemsArray.indexOf(item); // Guard for clicking on the currently selected tab. diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java index 97d81da263e..d98d90e1f4b 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java @@ -61,13 +61,22 @@ public TiUITabLayoutTabGroup(TabGroupProxy proxy, TiBaseActivity activity) public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); + enableTabsTouch(); + } - // Show/hide the tab bar. - this.mTabLayout.setVisibility(disable ? View.GONE : View.VISIBLE); - this.mTabLayout.requestLayout(); + private void enableTabsTouch() + { + LinearLayout tabStrip = ((LinearLayout) mTabLayout.getChildAt(0)); + if (tabStrip == null) { + return; + } - // Update top inset. (Will remove top inset if tab bar is "gone".) - this.insetsProvider.setTopBasedOn(this.mTabLayout); + for (int i = 0; i < mTabLayout.getTabCount(); i++) { + View tab = tabStrip.getChildAt(i); + if (tab != null) { + tab.setOnTouchListener((v, event) -> tabsDisabled); + } + } } @Override @@ -197,6 +206,7 @@ public void selectTabItemInController(int position) this.mTabLayout.clearOnTabSelectedListeners(); this.mTabLayout.getTabAt(position).select(); this.mTabLayout.addOnTabSelectedListener(this); + enableTabsTouch(); } @Override @@ -488,4 +498,14 @@ public static void scaleIconToFit(TabLayout.Tab tab) } } } + + public void showHideTabBar(boolean visible) + { + super.setTabGroupVisibilityWithAnimation(mTabLayout, visible); + } + + public void setTabGroupVisibility(boolean visible) + { + super.setTabGroupVisibility(mTabLayout, visible); + } } diff --git a/android/titanium/src/java/org/appcelerator/titanium/TiC.java b/android/titanium/src/java/org/appcelerator/titanium/TiC.java index 767b79e64fc..ddc9c1e2ddf 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/TiC.java +++ b/android/titanium/src/java/org/appcelerator/titanium/TiC.java @@ -762,6 +762,7 @@ public class TiC public static final String PROPERTY_TABS = "tabs"; public static final String PROPERTY_TAB_OPEN = "tabOpen"; public static final String PROPERTY_TAB_BAR_VISIBLE = "tabBarVisible"; + public static final String PROPERTY_DISABLE_TAB_NAVIGATION = "disableTabNavigation"; public static final String PROPERTY_TABS_BACKGROUND_COLOR = "tabsBackgroundColor"; public static final String PROPERTY_TABS_BACKGROUND_SELECTED_COLOR = "tabsBackgroundSelectedColor"; public static final String PROPERTY_TAB_MODE = "tabMode"; From a91c3e312edccac3cd7eec48407ff43de07ae57e Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Wed, 19 Mar 2025 02:59:40 +0530 Subject: [PATCH 04/15] chore: update doc --- apidoc/Titanium/UI/TabGroup.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/apidoc/Titanium/UI/TabGroup.yml b/apidoc/Titanium/UI/TabGroup.yml index 63eba7880c9..b2b83bfb6e7 100644 --- a/apidoc/Titanium/UI/TabGroup.yml +++ b/apidoc/Titanium/UI/TabGroup.yml @@ -206,6 +206,10 @@ methods: type: disableTabOptions platforms: [android] since: 3.6.0 + deprecated: + since: "12.7.0" + removed: "12.7.0" + notes: Use the property instead. - name: getActiveTab summary: Gets the currently-active tab. @@ -252,7 +256,7 @@ methods: description: | This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). To show tab bar without animation, set [tabBarVisible](Titanium.UI.TabGroup.tabBarVisible) property to `false`. - platforms: [iphone, ipad, macos] + platforms: [android, iphone, ipad, macos] since: "12.7.0" - name: hideTabBar @@ -260,7 +264,7 @@ methods: description: | This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). To hide tab bar without animation, set [tabBarVisible](Titanium.UI.TabGroup.tabBarVisible) property to `true`. - platforms: [iphone, ipad, macos] + platforms: [android, iphone, ipad, macos] since: "12.7.0" properties: @@ -312,6 +316,17 @@ properties: type: [String, Titanium.UI.Color] platforms: [iphone, ipad, android, macos] + - name: disableTabNavigation + summary: | + Disables the TabGroup swipe navigation and its Tabs click, without showing/hiding the TabGroup. + description: | + To show/hide the TabGroup with animation, use either or . + To show/hide the TabGroup without animation use . + type: Boolean + default: false + since: 12.7.0 + platforms: [android] + - name: paddingLeft summary: Left padding of bottom navigation description: | From 0e34bc53694051fed12e5eb1a13e1c26b48abd22 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Wed, 19 Mar 2025 15:33:09 +0530 Subject: [PATCH 05/15] fix(android): update translationY to allow animation calls --- .../titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java index 383ae31308f..a2718f96e6b 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java @@ -695,6 +695,12 @@ public void setTabGroupVisibility(View view, boolean visible) return; } + int translationY = view.getHeight(); + if (view instanceof TabLayout) { + translationY = -translationY; + } + + view.setTranslationY(visible ? 0 : translationY); view.setVisibility(visible ? View.VISIBLE : View.GONE); view.requestLayout(); updateInsets(view); From 4da0776ebd5c06633913f9a84a125fc5695bd082 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Wed, 19 Mar 2025 15:34:54 +0530 Subject: [PATCH 06/15] chore(android): use existing `enabled` property to disable tab navigation --- .../ti/modules/titanium/ui/TabGroupProxy.java | 24 +++++++++++-------- .../TiUIBottomNavigationTabGroup.java | 10 ++++++++ .../java/org/appcelerator/titanium/TiC.java | 1 - 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index ab6ee198a83..400dea97dc5 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -69,7 +69,7 @@ public class TabGroupProxy extends TiWindowProxy implements TiActivityWindow private String tabGroupTitle = null; private boolean autoTabTitle = false; private boolean isTabBarVisible = true; - private boolean isTabNavigationDisabled = false; + private boolean isTabGroupEnabled = true; public TabGroupProxy() { @@ -85,17 +85,23 @@ public int getTabIndex(TabProxy tabProxy) } @Kroll.setProperty - public void disableTabNavigation(boolean disable) + public void setEnabled(boolean enabled) { - isTabNavigationDisabled = disable; + isTabGroupEnabled = enabled; if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) { - bottomTabGroup.disableTabNavigation(isTabNavigationDisabled); + bottomTabGroup.disableTabNavigation(!isTabGroupEnabled); } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { - tabGroupDefault.disableTabNavigation(isTabNavigationDisabled); + tabGroupDefault.disableTabNavigation(!isTabGroupEnabled); } } + @Kroll.getProperty + public boolean getEnabled() + { + return isTabGroupEnabled; + } + @Kroll.method public void addTab(TabProxy tab) { @@ -296,10 +302,8 @@ public void handleCreationDict(KrollDict options) if (options.containsKeyAndNotNull(TiC.PROPERTY_TAB_BAR_VISIBLE)) { isTabBarVisible = options.optBoolean(TiC.PROPERTY_TAB_BAR_VISIBLE, isTabBarVisible); } - if (options.containsKeyAndNotNull(TiC.PROPERTY_DISABLE_TAB_NAVIGATION)) { - isTabNavigationDisabled = options.optBoolean( - TiC.PROPERTY_DISABLE_TAB_NAVIGATION, isTabNavigationDisabled - ); + if (options.containsKeyAndNotNull(TiC.PROPERTY_ENABLED)) { + isTabGroupEnabled = options.optBoolean(TiC.PROPERTY_ENABLED, isTabGroupEnabled); } } @@ -485,7 +489,7 @@ protected void handlePostOpen() isFocused = true; // Update UI if these properties are set before the native view is created. - disableTabNavigation(isTabNavigationDisabled); + setEnabled(isTabGroupEnabled); setTabBarVisible(isTabBarVisible); } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index 5b3c93abf70..547298db8f7 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -13,6 +13,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.RippleDrawable; import android.os.Build; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewParent; @@ -190,6 +191,15 @@ public void onLayoutChange( public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); + enableBottomTabs(); + } + + private void enableBottomTabs() + { + Menu menu = this.mBottomNavigationView.getMenu(); + for (int i = 0; i < menu.size(); i++) { + menu.getItem(i).setEnabled(!tabsDisabled); + } } @Override diff --git a/android/titanium/src/java/org/appcelerator/titanium/TiC.java b/android/titanium/src/java/org/appcelerator/titanium/TiC.java index ddc9c1e2ddf..767b79e64fc 100644 --- a/android/titanium/src/java/org/appcelerator/titanium/TiC.java +++ b/android/titanium/src/java/org/appcelerator/titanium/TiC.java @@ -762,7 +762,6 @@ public class TiC public static final String PROPERTY_TABS = "tabs"; public static final String PROPERTY_TAB_OPEN = "tabOpen"; public static final String PROPERTY_TAB_BAR_VISIBLE = "tabBarVisible"; - public static final String PROPERTY_DISABLE_TAB_NAVIGATION = "disableTabNavigation"; public static final String PROPERTY_TABS_BACKGROUND_COLOR = "tabsBackgroundColor"; public static final String PROPERTY_TABS_BACKGROUND_SELECTED_COLOR = "tabsBackgroundSelectedColor"; public static final String PROPERTY_TAB_MODE = "tabMode"; From 93c073a84e2e4f5152449df4e4d90915cf16ab0c Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Wed, 19 Mar 2025 19:01:08 +0530 Subject: [PATCH 07/15] fix(android): resolve conflicts --- .../ti/modules/titanium/ui/TabGroupProxy.java | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index 417393c9cef..3688d91c2a8 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -217,22 +217,6 @@ public void setActiveTab(Object tabOrIndex) } } - @Kroll.setProperty - public void setEnabled(Boolean enabled) - { - tabEnabled = enabled; - TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view; - if (tabGroup != null) { - tabGroup.setEnabled(enabled); - } - } - - @Kroll.getProperty - public Boolean getEnabled() - { - return tabEnabled; - } - private TabProxy getActiveTabProxy() { Object activeTab = getActiveTab(); From d90f4b21c329d9e5bb6227c0a5edbd277a50f17d Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Thu, 20 Mar 2025 14:46:44 +0530 Subject: [PATCH 08/15] revert(ios): keep showTabBar and hideTabBar methods with deprecation warning --- apidoc/Titanium/UI/Window.yml | 24 +++++++++++++++++++ .../Sources/Modules/TiUIWindowProxy.m | 12 ++++++++++ 2 files changed, 36 insertions(+) diff --git a/apidoc/Titanium/UI/Window.yml b/apidoc/Titanium/UI/Window.yml index 63c1497c5b1..017595c17ad 100644 --- a/apidoc/Titanium/UI/Window.yml +++ b/apidoc/Titanium/UI/Window.yml @@ -535,6 +535,18 @@ methods: default: "{ animated: true }" platforms: [iphone, ipad, macos] + - name: hideTabBar + summary: Hides the tab bar. Must be called before opening the window. + description: | + To hide the tab bar when opening a window as a child of a tab, call + `hideTabBar` or set `tabBarHidden` to `true` **before** opening the window. + If the window is not a child of a tab, this method has no effect. + platforms: [iphone, ipad, macos] + deprecated: + since: "12.7.0" + removed: "13.0.0" + notes: Deprecated in favor of property. + - name: open summary: Opens the window. parameters: @@ -583,6 +595,18 @@ methods: default: "{ animated: true }" platforms: [iphone, ipad, macos] + - name: showTabBar + summary: Shows the tab bar. Must be called before opening the window. + description: | + To show the tab bar when opening a window as a child of a tab, call + `showTabBar` or set `tabBarHidden` to `false` **before** opening the window. + If the window is not a child of a tab, this method has no effect. + platforms: [iphone, ipad, macos] + deprecated: + since: "12.7.0" + removed: "13.0.0" + notes: Deprecated in favor of property. + - name: showToolbar summary: Makes the bottom toolbar visible. description: | diff --git a/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m b/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m index 41c5dc97aae..c3b42ba5135 100644 --- a/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m +++ b/iphone/TitaniumKit/TitaniumKit/Sources/Modules/TiUIWindowProxy.m @@ -694,6 +694,18 @@ - (void)setTabBarHidden:(id)value NO); } +- (void)hideTabBar:(id)value +{ + DEPRECATED_REPLACED_REMOVED(@"UI.Window.hideTabBar()", @"12.7.0", @"13.0.0", @"UI.Window.tabBarHidden"); + [self setTabBarHidden:[NSNumber numberWithBool:YES]]; +} + +- (void)showTabBar:(id)value +{ + DEPRECATED_REPLACED_REMOVED(@"UI.Window.showTabBar()", @"12.7.0", @"13.0.0", @"UI.Window.tabBarHidden"); + [self setTabBarHidden:[NSNumber numberWithBool:NO]]; +} + - (void)refreshBackButton { ENSURE_UI_THREAD_0_ARGS; From e12f0627abf939581bbeb6102e14028e22bbec0f Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Thu, 20 Mar 2025 16:26:53 +0530 Subject: [PATCH 09/15] revert(android): keep `disableTabNavigation` method with deprecation warning --- .../ti/modules/titanium/ui/TabGroupProxy.java | 32 +++++++++++++++++++ .../TiUIBottomNavigationTabGroup.java | 7 ++-- .../tabgroup/TiUITabLayoutTabGroup.java | 9 ++++-- apidoc/Titanium/UI/TabGroup.yml | 4 +-- 4 files changed, 45 insertions(+), 7 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index 3688d91c2a8..b1784fcce1b 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -40,6 +40,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.HashMap; import ti.modules.titanium.ui.android.AndroidModule; import ti.modules.titanium.ui.widget.tabgroup.TiUIAbstractTabGroup; @@ -91,6 +92,37 @@ public int getTabIndex(TabProxy tabProxy) return tabs.indexOf(tabProxy); } + @Kroll.method + public void disableTabNavigation(Object params) + { + String message + = "Ti.UI.TabGroup.disableTabNavigation() has been deprecated in 12.7.0 in favor of" + + " Ti.UI.TabGroup.tabBarVisible and Ti.UI.TabGroup.enabled properties." + + " Ti.UI.TabGroup.disableTabNavigation() will be removed since 13.0.0."; + Log.w(TAG, message); + + if (params instanceof Boolean) { + boolean isEnabled = !TiConvert.toBoolean(params, false); + setEnabled(isEnabled); + setTabBarVisible(isEnabled); + return; + } + + if (params instanceof HashMap) { + KrollDict options = new KrollDict((HashMap) params); + + boolean isEnabled = !options.optBoolean(TiC.PROPERTY_ENABLED, false); + boolean isAnimated = options.optBoolean(TiC.PROPERTY_ANIMATED, false); + setEnabled(isEnabled); + + if (isAnimated) { + showHideTabBar(isEnabled); + } else { + setTabBarVisible(isEnabled); + } + } + } + @Kroll.setProperty public void setEnabled(boolean enabled) { diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index f086ea96ce2..4aeb2d6a683 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -191,10 +191,13 @@ public void onLayoutChange( public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); - enableBottomTabs(); + updateTabsInteraction(); } - private void enableBottomTabs() + /** + * Enable or disable tabs click event. + */ + private void updateTabsInteraction() { Menu menu = this.mBottomNavigationView.getMenu(); for (int i = 0; i < menu.size(); i++) { diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java index 885f9270f97..483881a4b3b 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java @@ -61,10 +61,13 @@ public TiUITabLayoutTabGroup(TabGroupProxy proxy, TiBaseActivity activity) public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); - enableTabsTouch(); + updateTabsInteraction(); } - private void enableTabsTouch() + /** + * Enable or disable tabs click event. + */ + private void updateTabsInteraction() { LinearLayout tabStrip = ((LinearLayout) mTabLayout.getChildAt(0)); if (tabStrip == null) { @@ -206,7 +209,7 @@ public void selectTabItemInController(int position) this.mTabLayout.clearOnTabSelectedListeners(); this.mTabLayout.getTabAt(position).select(); this.mTabLayout.addOnTabSelectedListener(this); - enableTabsTouch(); + updateTabsInteraction(); } @Override diff --git a/apidoc/Titanium/UI/TabGroup.yml b/apidoc/Titanium/UI/TabGroup.yml index 884ca33a8e5..ed1ce6a0b18 100644 --- a/apidoc/Titanium/UI/TabGroup.yml +++ b/apidoc/Titanium/UI/TabGroup.yml @@ -201,8 +201,8 @@ methods: since: 3.6.0 deprecated: since: "12.7.0" - removed: "12.7.0" - notes: Use the property instead. + removed: "13.0.0" + notes: Use the properties instead. - name: getActiveTab summary: Gets the currently-active tab. From f0597b104fd4206d7c5658f1fffb5219bdec8ee6 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Fri, 21 Mar 2025 15:23:05 +0530 Subject: [PATCH 10/15] chore(iOS): enable animation in tabBarVisible property on iOS <= 17 --- .../ti/modules/titanium/ui/TabGroupProxy.java | 2 +- apidoc/Titanium/UI/TabGroup.yml | 1 + iphone/Classes/TiUITabGroup.h | 2 + iphone/Classes/TiUITabGroup.m | 45 ++++++++++++++++--- 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index b1784fcce1b..ef47d63ae4b 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -98,7 +98,7 @@ public void disableTabNavigation(Object params) String message = "Ti.UI.TabGroup.disableTabNavigation() has been deprecated in 12.7.0 in favor of" + " Ti.UI.TabGroup.tabBarVisible and Ti.UI.TabGroup.enabled properties." - + " Ti.UI.TabGroup.disableTabNavigation() will be removed since 13.0.0."; + + " Ti.UI.TabGroup.disableTabNavigation() will be removed in 13.0.0."; Log.w(TAG, message); if (params instanceof Boolean) { diff --git a/apidoc/Titanium/UI/TabGroup.yml b/apidoc/Titanium/UI/TabGroup.yml index ed1ce6a0b18..5bff42492ef 100644 --- a/apidoc/Titanium/UI/TabGroup.yml +++ b/apidoc/Titanium/UI/TabGroup.yml @@ -651,6 +651,7 @@ properties: summary: Programmatically shows / hides the bottom tab bar of the tab group. description: | This method also updates iOS 18+ [Elevated Tab Bar on iPad](https://developer.apple.com/documentation/uikit/elevating-your-ipad-app-with-a-tab-bar-and-sidebar). + Notes: Animation effect is applied on iOS <= 17, but will be removed in SDK 13.0.0. type: Boolean default: true platforms: [android, iphone, ipad, macos] diff --git a/iphone/Classes/TiUITabGroup.h b/iphone/Classes/TiUITabGroup.h index 4bddf0ff948..947fed49a1b 100644 --- a/iphone/Classes/TiUITabGroup.h +++ b/iphone/Classes/TiUITabGroup.h @@ -20,6 +20,8 @@ TiColor *barColor; TiColor *navTintColor; NSMutableDictionary *theAttributes; + + BOOL isTabBarHidden; } - (UITabBarController *)tabController; diff --git a/iphone/Classes/TiUITabGroup.m b/iphone/Classes/TiUITabGroup.m index ad8c4b07177..24fd55968af 100644 --- a/iphone/Classes/TiUITabGroup.m +++ b/iphone/Classes/TiUITabGroup.m @@ -370,21 +370,52 @@ - (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizin - (void)setTabBarVisible_:(id)value { - [self hideTabBar:[TiUtils boolValue:value] animated:NO]; + bool isIOSVersionLowerThan18 = [TiUtils isIOSVersionLower:@"18.0"]; + if (isIOSVersionLowerThan18) { + DEPRECATED_REPLACED_REMOVED(@"UI.TabGroup.tabBarVisible's animation on iOS <= 17 is", + @"12.7.0", @"13.0.0", + @"UI.TabGroup.showTabBar/hideTabBar methods."); + } + + [self hideTabBar:![TiUtils boolValue:value def:YES] animated:isIOSVersionLowerThan18]; } - (void)hideTabBar:(BOOL)hidden animated:(BOOL)animated { + // iOS 18+ if (@available(iOS 18.0, *)) { [self.tabController setTabBarHidden:hidden animated:animated]; - } else { + return; + } + + // iOS 17 or below without animation. + if (!animated) { self.tabController.tabBar.hidden = hidden; + return; } -} -- (BOOL)tabBarIsVisible -{ - return self.tabController.tabBar.frame.origin.y < CGRectGetMaxY(self.tabController.view.frame); + // iOS 17 or below with animation. + if (isTabBarHidden == hidden) { + return; + } + + CGRect frame = self.tabController.tabBar.frame; + CGFloat height = frame.size.height; + + [UIView animateWithDuration:0.3 + animations:^{ + self.tabController.tabBar.frame = CGRectOffset(frame, 0, hidden ? height : -height); + } + completion:^(BOOL finished) { + if (finished) { + isTabBarHidden = hidden; + + // Adjust the bottom padding for the current tab view. + CGRect viewFrame = self.tabController.view.frame; + CGFloat viewHeight = viewFrame.size.height + (hidden ? height : -height); + self.tabController.view.frame = CGRectMake(0, 0, viewFrame.size.width, viewHeight); + } + }]; } - (void)setTabsBackgroundColor_:(id)value @@ -644,7 +675,7 @@ - (void)open:(id)args self.tabController.view.frame = self.bounds; [self addSubview:self.tabController.view]; - + isTabBarHidden = NO; [TiApp.controller.topPresentedController addChildViewController:self.tabController]; }, NO); From 43d51b6e6378fa7e045c16dd92efb5b7bc418e34 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Fri, 21 Mar 2025 23:13:48 +0530 Subject: [PATCH 11/15] chore(android): use override method `setEnabled` --- .../java/ti/modules/titanium/ui/TabGroupProxy.java | 7 ++----- .../ui/widget/tabgroup/TiUIAbstractTabGroup.java | 4 +--- .../tabgroup/TiUIBottomNavigationTabGroup.java | 13 +++---------- .../ui/widget/tabgroup/TiUITabLayoutTabGroup.java | 13 ++++--------- 4 files changed, 10 insertions(+), 27 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index ef47d63ae4b..5c98c5faf28 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -127,11 +127,8 @@ public void disableTabNavigation(Object params) public void setEnabled(boolean enabled) { isTabGroupEnabled = enabled; - - if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) { - bottomTabGroup.disableTabNavigation(!isTabGroupEnabled); - } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { - tabGroupDefault.disableTabNavigation(!isTabGroupEnabled); + if (view != null) { + ((TiUIAbstractTabGroup) view).disableTabNavigation(!isTabGroupEnabled); } } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java index a54ab2fc6c4..2d4fdf7009e 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java @@ -158,10 +158,8 @@ public abstract class TiUIAbstractTabGroup extends TiUIView /** * Enables/disables tab menu - * - * @param enabled value */ - public abstract void setEnabled(Boolean enabled); + public abstract void setEnabled(); // region protected fields protected final static String TAG = "TiUIAbstractTabGroup"; diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index 4aeb2d6a683..5eafbc54db1 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -191,13 +191,14 @@ public void onLayoutChange( public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); - updateTabsInteraction(); + setEnabled(); } /** * Enable or disable tabs click event. */ - private void updateTabsInteraction() + @Override + public void setEnabled() { Menu menu = this.mBottomNavigationView.getMenu(); for (int i = 0; i < menu.size(); i++) { @@ -516,14 +517,6 @@ public String getTabTitle(int index) return this.mBottomNavigationView.getMenu().getItem(index).getTitle().toString(); } - @Override - public void setEnabled(Boolean enabled) - { - for (int i = 0; i < this.mBottomNavigationView.getMenu().size(); i++) { - this.mBottomNavigationView.getMenu().getItem(i).setEnabled(enabled); - } - } - /** * After a menu item is clicked this method sends the proper index to the ViewPager to a select * a page. Also takes care of sending SELECTED/UNSELECTED events from the proper tabs. diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java index 483881a4b3b..eb066e283fb 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java @@ -61,13 +61,14 @@ public TiUITabLayoutTabGroup(TabGroupProxy proxy, TiBaseActivity activity) public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); - updateTabsInteraction(); + setEnabled(); } /** * Enable or disable tabs click event. */ - private void updateTabsInteraction() + @Override + public void setEnabled() { LinearLayout tabStrip = ((LinearLayout) mTabLayout.getChildAt(0)); if (tabStrip == null) { @@ -209,7 +210,7 @@ public void selectTabItemInController(int position) this.mTabLayout.clearOnTabSelectedListeners(); this.mTabLayout.getTabAt(position).select(); this.mTabLayout.addOnTabSelectedListener(this); - updateTabsInteraction(); + setEnabled(); } @Override @@ -395,12 +396,6 @@ public String getTabTitle(int index) return this.mTabLayout.getTabAt(index).getText().toString(); } - @Override - public void setEnabled(Boolean enabled) - { - - } - /** * After a tab is selected send the index for the ViewPager to select the proper page. * From 36bfe9f2c3d208e2ad21bafe9066b6a714aba685 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Fri, 21 Mar 2025 23:20:20 +0530 Subject: [PATCH 12/15] chore(android): move method to parent class --- .../widget/tabgroup/TiUIAbstractTabGroup.java | 26 ++++++++++++++++ .../TiUIBottomNavigationTabGroup.java | 31 ++----------------- 2 files changed, 28 insertions(+), 29 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java index 2d4fdf7009e..783abe81660 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java @@ -8,6 +8,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; +import android.animation.LayoutTransition; import android.app.Activity; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -31,12 +32,14 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import org.appcelerator.kroll.common.Log; import org.appcelerator.kroll.KrollDict; import org.appcelerator.kroll.KrollProxy; import org.appcelerator.titanium.TiBaseActivity; import org.appcelerator.titanium.TiC; +import org.appcelerator.titanium.TiDimension; import org.appcelerator.titanium.proxy.ActivityProxy; import org.appcelerator.titanium.proxy.TiViewProxy; import org.appcelerator.titanium.proxy.TiWindowProxy; @@ -44,6 +47,7 @@ import org.appcelerator.titanium.util.TiConvert; import org.appcelerator.titanium.util.TiIconDrawable; import org.appcelerator.titanium.util.TiUIHelper; +import org.appcelerator.titanium.view.TiCompositeLayout; import org.appcelerator.titanium.view.TiInsetsProvider; import org.appcelerator.titanium.view.TiUIView; @@ -729,6 +733,28 @@ public void setTabGroupVisibility(View view, boolean visible) updateInsets(view); } + public void setTabGroupViewPagerLayout(boolean visible, int viewHeight, boolean animated) + { + ViewParent viewParent = this.tabGroupViewPager.getParent(); + + // Resize the view pager (the tab's content) to compensate for shown/hidden tab bar. + // Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case. + if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) { + TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); + params.autoFillsWidth = true; + params.optionBottom = new TiDimension(!visible ? 0 : viewHeight, TiDimension.TYPE_BOTTOM); + + if (animated) { + LayoutTransition lt = new LayoutTransition(); + lt.enableTransitionType(LayoutTransition.CHANGING); + lt.setDuration(250); + this.tabGroupViewPager.setLayoutTransition(lt); + } + + this.tabGroupViewPager.setLayoutParams(params); + } + } + private void updateInsets(View view) { if (view instanceof BottomNavigationView) { diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index 5eafbc54db1..4eb1ad6a6e6 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -6,7 +6,6 @@ */ package ti.modules.titanium.ui.widget.tabgroup; -import android.animation.LayoutTransition; import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.ColorStateList; @@ -16,7 +15,6 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; -import android.view.ViewParent; import android.view.Window; import androidx.annotation.ColorInt; @@ -375,38 +373,13 @@ public void updateTabTitle(int index) public void showHideTabBar(boolean visible) { - ViewParent viewParent = this.tabGroupViewPager.getParent(); - - // Resize the view pager (the tab's content) to compensate for shown/hidden tab bar. - // Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case. - if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) { - TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); - params.autoFillsWidth = true; - params.optionBottom = new TiDimension(!visible ? 0 : mBottomNavigationHeightValue, TiDimension.TYPE_BOTTOM); - - LayoutTransition lt = new LayoutTransition(); - lt.enableTransitionType(LayoutTransition.CHANGING); - lt.setDuration(250); - this.tabGroupViewPager.setLayoutTransition(lt); - this.tabGroupViewPager.setLayoutParams(params); - } - + super.setTabGroupViewPagerLayout(visible, mBottomNavigationHeightValue, true); super.setTabGroupVisibilityWithAnimation(mBottomNavigationView, visible); } public void setTabGroupVisibility(boolean visible) { - ViewParent viewParent = this.tabGroupViewPager.getParent(); - - // Resize the view pager (the tab's content) to compensate for shown/hidden tab bar. - // Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case. - if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) { - TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams(); - params.autoFillsWidth = true; - params.optionBottom = new TiDimension(!visible ? 0 : mBottomNavigationHeightValue, TiDimension.TYPE_BOTTOM); - this.tabGroupViewPager.setLayoutParams(params); - } - + super.setTabGroupViewPagerLayout(visible, mBottomNavigationHeightValue, false); super.setTabGroupVisibility(mBottomNavigationView, visible); } From c8a9dd0d64e21f80d899cb6e3da6af4818d65d89 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Fri, 21 Mar 2025 23:22:48 +0530 Subject: [PATCH 13/15] fix(android): update properties when view layout is ready --- .../java/ti/modules/titanium/ui/TabGroupProxy.java | 6 ++++-- .../ui/widget/tabgroup/TiUIAbstractTabGroup.java | 7 +++++++ .../tabgroup/TiUIBottomNavigationTabGroup.java | 12 ++++++++++++ .../ui/widget/tabgroup/TiUITabLayoutTabGroup.java | 12 ++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index 5c98c5faf28..978b5f56eca 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -560,8 +560,10 @@ protected void handlePostOpen() isFocused = true; // Update UI if these properties are set before the native view is created. - setEnabled(isTabGroupEnabled); - setTabBarVisible(isTabBarVisible); + tabGroup.onViewSizeAvailable(() -> { + setEnabled(isTabGroupEnabled); + setTabBarVisible(isTabBarVisible); + }); } @Override diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java index 783abe81660..0a2bf2ebc0c 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java @@ -165,6 +165,13 @@ public abstract class TiUIAbstractTabGroup extends TiUIView */ public abstract void setEnabled(); + /** + * Returns the navigation-view associated with this TabGroup. + * Generally used to check if it's height is available or should be requested later. + * @return view + */ + public abstract void onViewSizeAvailable(Runnable runnable); + // region protected fields protected final static String TAG = "TiUIAbstractTabGroup"; protected static final String WARNING_LAYOUT_MESSAGE = diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java index 4eb1ad6a6e6..60815afc689 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigationTabGroup.java @@ -383,6 +383,18 @@ public void setTabGroupVisibility(boolean visible) super.setTabGroupVisibility(mBottomNavigationView, visible); } + @Override + public void onViewSizeAvailable(Runnable runnable) + { + if (mBottomNavigationView.getHeight() > 0) { + // Height is already available, run immediately. + runnable.run(); + } else { + // Height not available, post it to run after a layout pass. + mBottomNavigationView.post(runnable); + } + } + @SuppressLint("RestrictedApi") @Override public void updateBadge(int index) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java index eb066e283fb..ed983465505 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUITabLayoutTabGroup.java @@ -518,4 +518,16 @@ public void setTabGroupVisibility(boolean visible) { super.setTabGroupVisibility(mTabLayout, visible); } + + @Override + public void onViewSizeAvailable(Runnable runnable) + { + if (mTabLayout.getHeight() > 0) { + // Height is already available, run immediately. + runnable.run(); + } else { + // Height not available, post it to run after a layout pass. + mTabLayout.post(runnable); + } + } } From 6f298798e9e35c94ee6a9ac1e79e907797c92e78 Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Fri, 21 Mar 2025 23:26:28 +0530 Subject: [PATCH 14/15] feat(android): add tab-bar visibility handling in new bottom-navigation API --- .../ti/modules/titanium/ui/TabGroupProxy.java | 4 +++ .../widget/tabgroup/TiUIBottomNavigation.java | 27 +++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java index 978b5f56eca..c75fa43485c 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java @@ -166,6 +166,8 @@ public void setTabBarVisible(boolean visible) bottomTabGroup.setTabGroupVisibility(visible); } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { tabGroupDefault.setTabGroupVisibility(visible); + } else if (view instanceof TiUIBottomNavigation bottomNavigation) { + bottomNavigation.setTabGroupVisibility(visible); } } @@ -187,6 +189,8 @@ private void showHideTabBar(boolean visible) bottomTabGroup.showHideTabBar(visible); } else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) { tabGroupDefault.showHideTabBar(visible); + } else if (view instanceof TiUIBottomNavigation bottomNavigation) { + bottomNavigation.showHideTabBar(visible); } } diff --git a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigation.java b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigation.java index 8a7d3105c50..c1262369593 100644 --- a/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigation.java +++ b/android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigation.java @@ -183,6 +183,7 @@ public void addViews(TiBaseActivity activity) public void disableTabNavigation(boolean disable) { super.disableTabNavigation(disable); + setEnabled(); } @Override @@ -504,10 +505,10 @@ public String getTabTitle(int index) } @Override - public void setEnabled(Boolean enabled) + public void setEnabled() { for (int i = 0; i < this.bottomNavigation.getMenu().size(); i++) { - this.bottomNavigation.getMenu().getItem(i).setEnabled(enabled); + this.bottomNavigation.getMenu().getItem(i).setEnabled(!tabsDisabled); } } @@ -550,4 +551,26 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item) ((TabGroupProxy) getProxy()).onTabSelected(item.getItemId()); return true; } + + public void showHideTabBar(boolean visible) + { + super.setTabGroupVisibilityWithAnimation(bottomNavigation, visible); + } + + public void setTabGroupVisibility(boolean visible) + { + super.setTabGroupVisibility(bottomNavigation, visible); + } + + @Override + public void onViewSizeAvailable(Runnable runnable) + { + if (bottomNavigation.getHeight() > 0) { + // Height is already available, run immediately. + runnable.run(); + } else { + // Height not available, post it to run after a layout pass. + bottomNavigation.post(runnable); + } + } } From e4d122fa99a6a6c6476f5d28ed687db4c810fd9a Mon Sep 17 00:00:00 2001 From: prashantsaini1 Date: Mon, 24 Mar 2025 12:33:30 +0530 Subject: [PATCH 15/15] chore: address PR reviews --- apidoc/Titanium/UI/TabGroup.yml | 2 -- iphone/Classes/TiUITabGroup.m | 11 ++++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/apidoc/Titanium/UI/TabGroup.yml b/apidoc/Titanium/UI/TabGroup.yml index 5bff42492ef..35b0e2ba4a5 100644 --- a/apidoc/Titanium/UI/TabGroup.yml +++ b/apidoc/Titanium/UI/TabGroup.yml @@ -201,8 +201,6 @@ methods: since: 3.6.0 deprecated: since: "12.7.0" - removed: "13.0.0" - notes: Use the properties instead. - name: getActiveTab summary: Gets the currently-active tab. diff --git a/iphone/Classes/TiUITabGroup.m b/iphone/Classes/TiUITabGroup.m index 24fd55968af..9ec41f6955a 100644 --- a/iphone/Classes/TiUITabGroup.m +++ b/iphone/Classes/TiUITabGroup.m @@ -370,31 +370,28 @@ - (void)tabBarController:(UITabBarController *)tabBarController didEndCustomizin - (void)setTabBarVisible_:(id)value { - bool isIOSVersionLowerThan18 = [TiUtils isIOSVersionLower:@"18.0"]; - if (isIOSVersionLowerThan18) { - DEPRECATED_REPLACED_REMOVED(@"UI.TabGroup.tabBarVisible's animation on iOS <= 17 is", + BOOL isiOS17OrLower = [TiUtils isIOSVersionLower:@"18.0"]; + if (isiOS17OrLower) { + DEPRECATED_REPLACED_REMOVED(@"UI.TabGroup.tabBarVisible animation on iOS < 18 is", @"12.7.0", @"13.0.0", @"UI.TabGroup.showTabBar/hideTabBar methods."); } - [self hideTabBar:![TiUtils boolValue:value def:YES] animated:isIOSVersionLowerThan18]; + [self hideTabBar:![TiUtils boolValue:value def:YES] animated:isiOS17OrLower]; } - (void)hideTabBar:(BOOL)hidden animated:(BOOL)animated { - // iOS 18+ if (@available(iOS 18.0, *)) { [self.tabController setTabBarHidden:hidden animated:animated]; return; } - // iOS 17 or below without animation. if (!animated) { self.tabController.tabBar.hidden = hidden; return; } - // iOS 17 or below with animation. if (isTabBarHidden == hidden) { return; }