Skip to content

Commit d84bb4d

Browse files
feat: TabGroup show/hide support, iOS 18+ elevated tab bar (#14173)
* feat(iOS): add support to show/hide iOS 18+ elevated tab bar * chore: removed methods which actually do not show/hide tab-bar * feat(android): add new methods and fix existing properties handling use showTabBar/hideTabBar for animation, use tabBarVisible without animation * chore: update doc * fix(android): update translationY to allow animation calls * chore(android): use existing `enabled` property to disable tab navigation * fix(android): resolve conflicts * revert(ios): keep showTabBar and hideTabBar methods with deprecation warning * revert(android): keep `disableTabNavigation` method with deprecation warning * chore(iOS): enable animation in tabBarVisible property on iOS <= 17 * chore(android): use override method `setEnabled` * chore(android): move method to parent class * fix(android): update properties when view layout is ready * feat(android): add tab-bar visibility handling in new bottom-navigation API * chore: address PR reviews
1 parent 356b4a2 commit d84bb4d

File tree

13 files changed

+370
-122
lines changed

13 files changed

+370
-122
lines changed

android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java

+80-35
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ public class TabGroupProxy extends TiWindowProxy implements TiActivityWindow
7676
private Object selectedTab; // NOTE: Can be TabProxy or Number
7777
private String tabGroupTitle = null;
7878
private boolean autoTabTitle = false;
79-
private boolean tabEnabled = true;
79+
private boolean isTabBarVisible = true;
80+
private boolean isTabGroupEnabled = true;
8081

8182
public TabGroupProxy()
8283
{
@@ -94,24 +95,49 @@ public int getTabIndex(TabProxy tabProxy)
9495
@Kroll.method
9596
public void disableTabNavigation(Object params)
9697
{
98+
String message
99+
= "Ti.UI.TabGroup.disableTabNavigation() has been deprecated in 12.7.0 in favor of"
100+
+ " Ti.UI.TabGroup.tabBarVisible and Ti.UI.TabGroup.enabled properties."
101+
+ " Ti.UI.TabGroup.disableTabNavigation() will be removed in 13.0.0.";
102+
Log.w(TAG, message);
103+
97104
if (params instanceof Boolean) {
98-
TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view;
99-
if (tabGroup != null) {
100-
tabGroup.disableTabNavigation(TiConvert.toBoolean(params, false));
101-
}
102-
} else if (params instanceof HashMap<?, ?>) {
105+
boolean isEnabled = !TiConvert.toBoolean(params, false);
106+
setEnabled(isEnabled);
107+
setTabBarVisible(isEnabled);
108+
return;
109+
}
110+
111+
if (params instanceof HashMap<?, ?>) {
103112
KrollDict options = new KrollDict((HashMap<String, Object>) params);
104-
TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view;
105-
if (tabGroup != null) {
106-
if (options.getBoolean(TiC.PROPERTY_ANIMATED)) {
107-
setTabBarVisible(options.getBoolean(TiC.PROPERTY_ENABLED));
108-
} else {
109-
tabGroup.disableTabNavigation(options.getBoolean(TiC.PROPERTY_ENABLED));
110-
}
113+
114+
boolean isEnabled = !options.optBoolean(TiC.PROPERTY_ENABLED, false);
115+
boolean isAnimated = options.optBoolean(TiC.PROPERTY_ANIMATED, false);
116+
setEnabled(isEnabled);
117+
118+
if (isAnimated) {
119+
showHideTabBar(isEnabled);
120+
} else {
121+
setTabBarVisible(isEnabled);
111122
}
112123
}
113124
}
114125

126+
@Kroll.setProperty
127+
public void setEnabled(boolean enabled)
128+
{
129+
isTabGroupEnabled = enabled;
130+
if (view != null) {
131+
((TiUIAbstractTabGroup) view).disableTabNavigation(!isTabGroupEnabled);
132+
}
133+
}
134+
135+
@Kroll.getProperty
136+
public boolean getEnabled()
137+
{
138+
return isTabGroupEnabled;
139+
}
140+
115141
@Kroll.method
116142
public void addTab(TabProxy tab)
117143
{
@@ -134,10 +160,37 @@ public void addTab(TabProxy tab)
134160
@Kroll.setProperty
135161
public void setTabBarVisible(boolean visible)
136162
{
137-
TiUIBottomNavigationTabGroup tabGroup = (TiUIBottomNavigationTabGroup) view;
163+
isTabBarVisible = visible;
138164

139-
if (tabGroup != null) {
140-
tabGroup.setTabBarVisible(visible);
165+
if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) {
166+
bottomTabGroup.setTabGroupVisibility(visible);
167+
} else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) {
168+
tabGroupDefault.setTabGroupVisibility(visible);
169+
} else if (view instanceof TiUIBottomNavigation bottomNavigation) {
170+
bottomNavigation.setTabGroupVisibility(visible);
171+
}
172+
}
173+
174+
@Kroll.method
175+
public void showTabBar()
176+
{
177+
showHideTabBar(true);
178+
}
179+
180+
@Kroll.method
181+
public void hideTabBar()
182+
{
183+
showHideTabBar(false);
184+
}
185+
186+
private void showHideTabBar(boolean visible)
187+
{
188+
if (view instanceof TiUIBottomNavigationTabGroup bottomTabGroup) {
189+
bottomTabGroup.showHideTabBar(visible);
190+
} else if (view instanceof TiUITabLayoutTabGroup tabGroupDefault) {
191+
tabGroupDefault.showHideTabBar(visible);
192+
} else if (view instanceof TiUIBottomNavigation bottomNavigation) {
193+
bottomNavigation.showHideTabBar(visible);
141194
}
142195
}
143196

@@ -197,22 +250,6 @@ public void setActiveTab(Object tabOrIndex)
197250
}
198251
}
199252

200-
@Kroll.setProperty
201-
public void setEnabled(Boolean enabled)
202-
{
203-
tabEnabled = enabled;
204-
TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view;
205-
if (tabGroup != null) {
206-
tabGroup.setEnabled(enabled);
207-
}
208-
}
209-
210-
@Kroll.getProperty
211-
public Boolean getEnabled()
212-
{
213-
return tabEnabled;
214-
}
215-
216253
private TabProxy getActiveTabProxy()
217254
{
218255
Object activeTab = getActiveTab();
@@ -244,8 +281,7 @@ public void setTabs(Object obj)
244281
}
245282
tabs.clear();
246283

247-
if (obj instanceof Object[]) {
248-
Object[] objArray = (Object[]) obj;
284+
if (obj instanceof Object[] objArray) {
249285
for (Object tabProxy : objArray) {
250286
if (tabProxy instanceof TabProxy) {
251287
addTab((TabProxy) tabProxy);
@@ -303,8 +339,11 @@ public void handleCreationDict(KrollDict options)
303339
if (options.containsKeyAndNotNull(TiC.PROPERTY_ACTIVE_TAB)) {
304340
setActiveTab(options.get(TiC.PROPERTY_ACTIVE_TAB));
305341
}
342+
if (options.containsKeyAndNotNull(TiC.PROPERTY_TAB_BAR_VISIBLE)) {
343+
isTabBarVisible = options.optBoolean(TiC.PROPERTY_TAB_BAR_VISIBLE, isTabBarVisible);
344+
}
306345
if (options.containsKeyAndNotNull(TiC.PROPERTY_ENABLED)) {
307-
setEnabled(options.getBoolean(TiC.PROPERTY_ENABLED));
346+
isTabGroupEnabled = options.optBoolean(TiC.PROPERTY_ENABLED, isTabGroupEnabled);
308347
}
309348
}
310349

@@ -523,6 +562,12 @@ protected void handlePostOpen()
523562
// Prevent any duplicate events from firing by marking
524563
// this group has having focus.
525564
isFocused = true;
565+
566+
// Update UI if these properties are set before the native view is created.
567+
tabGroup.onViewSizeAvailable(() -> {
568+
setEnabled(isTabGroupEnabled);
569+
setTabBarVisible(isTabBarVisible);
570+
});
526571
}
527572

528573
@Override

android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIAbstractTabGroup.java

+99-3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
*/
77
package ti.modules.titanium.ui.widget.tabgroup;
88

9+
import android.animation.Animator;
10+
import android.animation.AnimatorListenerAdapter;
11+
import android.animation.LayoutTransition;
912
import android.app.Activity;
1013
import android.content.res.ColorStateList;
1114
import android.content.res.TypedArray;
@@ -29,19 +32,22 @@
2932
import android.view.MotionEvent;
3033
import android.view.View;
3134
import android.view.ViewGroup;
35+
import android.view.ViewParent;
3236

3337
import org.appcelerator.kroll.common.Log;
3438
import org.appcelerator.kroll.KrollDict;
3539
import org.appcelerator.kroll.KrollProxy;
3640
import org.appcelerator.titanium.TiBaseActivity;
3741
import org.appcelerator.titanium.TiC;
42+
import org.appcelerator.titanium.TiDimension;
3843
import org.appcelerator.titanium.proxy.ActivityProxy;
3944
import org.appcelerator.titanium.proxy.TiViewProxy;
4045
import org.appcelerator.titanium.proxy.TiWindowProxy;
4146
import org.appcelerator.titanium.util.TiColorHelper;
4247
import org.appcelerator.titanium.util.TiConvert;
4348
import org.appcelerator.titanium.util.TiIconDrawable;
4449
import org.appcelerator.titanium.util.TiUIHelper;
50+
import org.appcelerator.titanium.view.TiCompositeLayout;
4551
import org.appcelerator.titanium.view.TiInsetsProvider;
4652
import org.appcelerator.titanium.view.TiUIView;
4753

@@ -52,6 +58,8 @@
5258
import ti.modules.titanium.ui.TabGroupProxy;
5359
import ti.modules.titanium.ui.TabProxy;
5460
import com.google.android.material.R;
61+
import com.google.android.material.bottomnavigation.BottomNavigationView;
62+
import com.google.android.material.tabs.TabLayout;
5563

5664
/**
5765
* Abstract class representing Tab Navigation in Titanium. Abstract methods in it
@@ -154,10 +162,15 @@ public abstract class TiUIAbstractTabGroup extends TiUIView
154162

155163
/**
156164
* Enables/disables tab menu
157-
*
158-
* @param enabled value
159165
*/
160-
public abstract void setEnabled(Boolean enabled);
166+
public abstract void setEnabled();
167+
168+
/**
169+
* Returns the navigation-view associated with this TabGroup.
170+
* Generally used to check if it's height is available or should be requested later.
171+
* @return view
172+
*/
173+
public abstract void onViewSizeAvailable(Runnable runnable);
161174

162175
// region protected fields
163176
protected final static String TAG = "TiUIAbstractTabGroup";
@@ -675,6 +688,89 @@ public static ColorStateList createRippleColorStateListFrom(@ColorInt int colorI
675688
return new ColorStateList(rippleStates, rippleColors);
676689
}
677690

691+
public void setTabGroupVisibilityWithAnimation(View view, boolean visible)
692+
{
693+
if (this.proxy == null || this.proxy.peekView() == null) {
694+
return;
695+
}
696+
697+
int translationY = view.getHeight();
698+
if (view instanceof TabLayout) {
699+
translationY = -translationY;
700+
}
701+
702+
view.animate()
703+
.translationY(visible ? 0 : translationY)
704+
.setDuration(250)
705+
.setListener(new AnimatorListenerAdapter() {
706+
@Override
707+
public void onAnimationStart(Animator animation)
708+
{
709+
if (visible) {
710+
view.setVisibility(View.VISIBLE);
711+
}
712+
}
713+
714+
@Override
715+
public void onAnimationEnd(Animator animation)
716+
{
717+
if (!visible) {
718+
view.setVisibility(View.GONE);
719+
}
720+
}
721+
});
722+
723+
updateInsets(view);
724+
}
725+
726+
public void setTabGroupVisibility(View view, boolean visible)
727+
{
728+
if (this.proxy == null || this.proxy.peekView() == null) {
729+
return;
730+
}
731+
732+
int translationY = view.getHeight();
733+
if (view instanceof TabLayout) {
734+
translationY = -translationY;
735+
}
736+
737+
view.setTranslationY(visible ? 0 : translationY);
738+
view.setVisibility(visible ? View.VISIBLE : View.GONE);
739+
view.requestLayout();
740+
updateInsets(view);
741+
}
742+
743+
public void setTabGroupViewPagerLayout(boolean visible, int viewHeight, boolean animated)
744+
{
745+
ViewParent viewParent = this.tabGroupViewPager.getParent();
746+
747+
// Resize the view pager (the tab's content) to compensate for shown/hidden tab bar.
748+
// Not applicable if Titanium "extendSafeArea" is true, because tab bar overlaps content in this case.
749+
if ((viewParent instanceof View) && ((View) viewParent).getFitsSystemWindows()) {
750+
TiCompositeLayout.LayoutParams params = new TiCompositeLayout.LayoutParams();
751+
params.autoFillsWidth = true;
752+
params.optionBottom = new TiDimension(!visible ? 0 : viewHeight, TiDimension.TYPE_BOTTOM);
753+
754+
if (animated) {
755+
LayoutTransition lt = new LayoutTransition();
756+
lt.enableTransitionType(LayoutTransition.CHANGING);
757+
lt.setDuration(250);
758+
this.tabGroupViewPager.setLayoutTransition(lt);
759+
}
760+
761+
this.tabGroupViewPager.setLayoutParams(params);
762+
}
763+
}
764+
765+
private void updateInsets(View view)
766+
{
767+
if (view instanceof BottomNavigationView) {
768+
this.insetsProvider.setBottomBasedOn(view);
769+
} else {
770+
this.insetsProvider.setTopBasedOn(view);
771+
}
772+
}
773+
678774
/**
679775
* Implementation of the FragmentPagerAdapter
680776
*/

android/modules/ui/src/java/ti/modules/titanium/ui/widget/tabgroup/TiUIBottomNavigation.java

+25-2
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ public void addViews(TiBaseActivity activity)
183183
public void disableTabNavigation(boolean disable)
184184
{
185185
super.disableTabNavigation(disable);
186+
setEnabled();
186187
}
187188

188189
@Override
@@ -504,10 +505,10 @@ public String getTabTitle(int index)
504505
}
505506

506507
@Override
507-
public void setEnabled(Boolean enabled)
508+
public void setEnabled()
508509
{
509510
for (int i = 0; i < this.bottomNavigation.getMenu().size(); i++) {
510-
this.bottomNavigation.getMenu().getItem(i).setEnabled(enabled);
511+
this.bottomNavigation.getMenu().getItem(i).setEnabled(!tabsDisabled);
511512
}
512513
}
513514

@@ -550,4 +551,26 @@ public boolean onNavigationItemSelected(@NonNull MenuItem item)
550551
((TabGroupProxy) getProxy()).onTabSelected(item.getItemId());
551552
return true;
552553
}
554+
555+
public void showHideTabBar(boolean visible)
556+
{
557+
super.setTabGroupVisibilityWithAnimation(bottomNavigation, visible);
558+
}
559+
560+
public void setTabGroupVisibility(boolean visible)
561+
{
562+
super.setTabGroupVisibility(bottomNavigation, visible);
563+
}
564+
565+
@Override
566+
public void onViewSizeAvailable(Runnable runnable)
567+
{
568+
if (bottomNavigation.getHeight() > 0) {
569+
// Height is already available, run immediately.
570+
runnable.run();
571+
} else {
572+
// Height not available, post it to run after a layout pass.
573+
bottomNavigation.post(runnable);
574+
}
575+
}
553576
}

0 commit comments

Comments
 (0)