Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: TabGroup show/hide support, iOS 18+ elevated tab bar #14173

Merged
merged 16 commits into from
Mar 24, 2025
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 80 additions & 35 deletions android/modules/ui/src/java/ti/modules/titanium/ui/TabGroupProxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,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 tabEnabled = true;
private boolean isTabBarVisible = true;
private boolean isTabGroupEnabled = true;

public TabGroupProxy()
{
Expand All @@ -94,24 +95,49 @@ public int getTabIndex(TabProxy 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 in 13.0.0.";
Log.w(TAG, message);

if (params instanceof Boolean) {
TiUIAbstractTabGroup tabGroup = (TiUIAbstractTabGroup) view;
if (tabGroup != null) {
tabGroup.disableTabNavigation(TiConvert.toBoolean(params, false));
}
} else if (params instanceof HashMap<?, ?>) {
boolean isEnabled = !TiConvert.toBoolean(params, false);
setEnabled(isEnabled);
setTabBarVisible(isEnabled);
return;
}

if (params instanceof HashMap<?, ?>) {
KrollDict options = new KrollDict((HashMap<String, Object>) 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));
}

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)
{
isTabGroupEnabled = enabled;
if (view != null) {
((TiUIAbstractTabGroup) view).disableTabNavigation(!isTabGroupEnabled);
}
}

@Kroll.getProperty
public boolean getEnabled()
{
return isTabGroupEnabled;
}

@Kroll.method
public void addTab(TabProxy tab)
{
Expand All @@ -134,10 +160,37 @@ 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);
} else if (view instanceof TiUIBottomNavigation bottomNavigation) {
bottomNavigation.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);
} else if (view instanceof TiUIBottomNavigation bottomNavigation) {
bottomNavigation.showHideTabBar(visible);
}
}

Expand Down Expand Up @@ -197,22 +250,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();
Expand Down Expand Up @@ -244,8 +281,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);
Expand Down Expand Up @@ -303,8 +339,11 @@ 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_ENABLED)) {
setEnabled(options.getBoolean(TiC.PROPERTY_ENABLED));
isTabGroupEnabled = options.optBoolean(TiC.PROPERTY_ENABLED, isTabGroupEnabled);
}
}

Expand Down Expand Up @@ -523,6 +562,12 @@ 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.
tabGroup.onViewSizeAvailable(() -> {
setEnabled(isTabGroupEnabled);
setTabBarVisible(isTabBarVisible);
});
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
*/
package ti.modules.titanium.ui.widget.tabgroup;

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;
Expand All @@ -29,19 +32,22 @@
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;
import org.appcelerator.titanium.util.TiColorHelper;
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;

Expand All @@ -52,6 +58,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
Expand Down Expand Up @@ -154,10 +162,15 @@ public abstract class TiUIAbstractTabGroup extends TiUIView

/**
* Enables/disables tab menu
*
* @param enabled value
*/
public abstract void setEnabled(Boolean enabled);
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";
Expand Down Expand Up @@ -675,6 +688,89 @@ 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;
}

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);
}

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) {
this.insetsProvider.setBottomBasedOn(view);
} else {
this.insetsProvider.setTopBasedOn(view);
}
}

/**
* Implementation of the FragmentPagerAdapter
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ public void addViews(TiBaseActivity activity)
public void disableTabNavigation(boolean disable)
{
super.disableTabNavigation(disable);
setEnabled();
}

@Override
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
}
}
Loading
Loading