refactor: delegate navigation to controller and helper

This commit is contained in:
Tomás Villegas
2026-02-25 01:56:03 -03:00
parent 2db9cb80d1
commit d1851bcc5a
4 changed files with 271 additions and 74 deletions

View File

@@ -10,7 +10,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.widget.FrameLayout;
@@ -39,6 +38,9 @@ import com.cappielloantonio.tempo.databinding.ActivityMainBinding;
import com.cappielloantonio.tempo.github.utils.UpdateUtil;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.ui.activity.base.BaseActivity;
import com.cappielloantonio.tempo.ui.activity.base.NavigationController;
import com.cappielloantonio.tempo.ui.activity.base.NavigationDelegate;
import com.cappielloantonio.tempo.ui.activity.base.NavigationHelper;
import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
@@ -62,6 +64,7 @@ public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivityLogs";
public ActivityMainBinding bind;
private NavigationHelper navigationHelper;
private MainViewModel mainViewModel;
private FragmentManager fragmentManager;
@@ -71,7 +74,7 @@ public class MainActivity extends BaseActivity {
public NavController navController;
private DrawerLayout drawerLayout;
private NavigationView navigationView;
private BottomSheetBehavior bottomSheetBehavior;
public BottomSheetBehavior bottomSheetBehavior;
public boolean isLandscape = false;
private AssetLinkNavigator assetLinkNavigator;
private AssetLinkUtil.AssetLink pendingAssetLink;
@@ -79,6 +82,10 @@ public class MainActivity extends BaseActivity {
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
private Intent pendingDownloadPlaybackIntent;
public ActivityMainBinding getBinding() {
return bind;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
@@ -126,6 +133,10 @@ public class MainActivity extends BaseActivity {
super.onDestroy();
connectivityStatusReceiverManager(false);
bind = null;
NavigationDelegate.getInstance().unbind();
if (navigationHelper != null) {
navigationHelper.release();
}
}
@Override
@@ -148,7 +159,16 @@ public class MainActivity extends BaseActivity {
fragmentManager = getSupportFragmentManager();
initBottomSheet();
initNavigation();
// All of the navigation stuff, contained here
NavigationDelegate.getInstance().bind(this);
navigationHelper = NavigationHelper.init(this);
// This is for "backward compatibility" with old code
navController = navigationHelper.getNavController();
bottomNavigationView = navigationHelper.getBottomNavigationView();
bottomNavigationViewFrame = navigationHelper.getBottomNavigationViewFrame();
drawerLayout = navigationHelper.getDrawerLayout();
if (Preferences.getPassword() != null || (Preferences.getToken() != null && Preferences.getSalt() != null)) {
goFromLogin();
@@ -259,103 +279,42 @@ public class MainActivity extends BaseActivity {
bind.bottomNavigation.setTranslationY(slideY);
}
private void initNavigation() {
bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationViewFrame = findViewById(R.id.bottom_navigation_frame);
navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment);
navController = Objects.requireNonNull(navHostFragment).getNavController();
// This is the lateral slide-in drawer
drawerLayout = findViewById(R.id.drawer_layout);
navigationView = findViewById(R.id.nav_view);
/*
* In questo modo intercetto il cambio schermata tramite navbar e se il bottom sheet è aperto,
* lo chiudo
*/
navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED && (
destination.getId() == R.id.homeFragment ||
destination.getId() == R.id.libraryFragment ||
destination.getId() == R.id.downloadFragment)
) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
NavigationUI.setupWithNavController(bottomNavigationView, navController);
NavigationUI.setupWithNavController(navigationView, navController);
}
public void setBottomNavigationBarVisibility(boolean visibility) {
if (visibility) {
bottomNavigationView.setVisibility(View.VISIBLE);
bottomNavigationViewFrame.setVisibility(View.VISIBLE);
} else {
bottomNavigationView.setVisibility(View.GONE);
bottomNavigationViewFrame.setVisibility(View.GONE);
}
navigationHelper.setBottomNavigationBarVisibility(visibility);
}
public void toggleBottomNavigationBarVisibilityOnOrientationChange() {
// Ignore orientation change, bottom navbar always hidden
if (Preferences.getHideBottomNavbarOnPortrait()) {
setBottomNavigationBarVisibility(false);
navigationHelper.setBottomNavigationBarVisibility(false);
setPortraitPlayerBottomSheetPeekHeight(56);
setSystemBarsVisibility(!isLandscape);
navigationHelper.setSystemBarsVisibility(this, !isLandscape);
return;
}
if (!isLandscape) {
// Show app navbar + show system bars
setPortraitPlayerBottomSheetPeekHeight(136);
setBottomNavigationBarVisibility(true);
setSystemBarsVisibility(true);
navigationHelper.setBottomNavigationBarVisibility(true);
navigationHelper.setSystemBarsVisibility(this, true);
} else {
// Hide app navbar + hide system bars
setPortraitPlayerBottomSheetPeekHeight(56);
setBottomNavigationBarVisibility(false);
setSystemBarsVisibility(false);
navigationHelper.setBottomNavigationBarVisibility(false);
navigationHelper.setSystemBarsVisibility(this, false);
}
}
public void setNavigationDrawerLock(boolean locked) {
int mode = locked
? DrawerLayout.LOCK_MODE_LOCKED_CLOSED
: DrawerLayout.LOCK_MODE_UNLOCKED;
drawerLayout.setDrawerLockMode(mode);
navigationHelper.setNavigationDrawerLock(locked);
}
public void toggleNavigationDrawerLockOnOrientationChange() {
// Ignore orientation check, drawer always unlocked
if (Preferences.getEnableDrawerOnPortrait()) {
setNavigationDrawerLock(false);
return;
}
if (!isLandscape) {
setNavigationDrawerLock(true);
} else {
setNavigationDrawerLock(false);
}
navigationHelper.toggleNavigationDrawerLockOnOrientationChange(this, isLandscape);
}
public void setSystemBarsVisibility(boolean visibility) {
WindowInsetsControllerCompat insetsController;
View decorView = getWindow().getDecorView();
insetsController = new WindowInsetsControllerCompat(getWindow(), decorView);
if (visibility) {
WindowCompat.setDecorFitsSystemWindows(getWindow(), true);
insetsController.show(WindowInsetsCompat.Type.navigationBars());
insetsController.show(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT);
} else {
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
insetsController.hide(WindowInsetsCompat.Type.navigationBars());
insetsController.hide(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
navigationHelper.setSystemBarsVisibility(this, visibility);
}
private void setPortraitPlayerBottomSheetPeekHeight(int peekHeight) {

View File

@@ -0,0 +1,12 @@
package com.cappielloantonio.tempo.ui.activity.base;
import androidx.annotation.OptIn;
import androidx.media3.common.util.UnstableApi;
public interface NavigationController {
@OptIn(markerClass = UnstableApi.class)
default boolean isVisible() {
return NavigationDelegate.isVisible();
}
}

View File

@@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.ui.activity.base;
import android.view.View;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.databinding.ActivityMainBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
/*
The goal of this class is to stop instanciating MainActivity on each fragment */
@UnstableApi
public final class NavigationDelegate {
private static final NavigationDelegate INSTANCE = new NavigationDelegate();
private MainActivity activity;
private boolean visible = true;
private NavigationDelegate() {}
public static NavigationDelegate getInstance() {
return INSTANCE;
}
/* Call inside onCreate() in MainActivity giving as argument `this` */
public void bind(MainActivity activity) {
this.activity = activity;
}
/* Call inside onDestroy() in MainActivity*/
public void unbind() {
this.activity = null;
}
public static boolean isVisible() {
return INSTANCE.visible;
}
/** Change visibility and update the UI on the UI thread. */
public void setVisibility(final boolean visible) {
this.visible = visible;
ActivityMainBinding bind = activity.getBinding();
bind.bottomNavigation.setVisibility(visible
? View.VISIBLE
: View.GONE);
}
}

View File

@@ -0,0 +1,178 @@
package com.cappielloantonio.tempo.ui.activity.base;
import android.content.Context;
import android.view.View;
import android.view.Window;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.core.view.WindowInsetsControllerCompat;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.navigation.ui.NavigationUI;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.navigation.NavigationView;
import java.util.Objects;
public class NavigationHelper {
/* UI components */
private BottomNavigationView bottomNavigationView;
private FrameLayout bottomNavigationViewFrame;
private DrawerLayout drawerLayout;
/* Navigation components */
private NavigationView navigationView;
private NavHostFragment navHostFragment;
private NavController navController;
/* States that need to be remembered */
private final Context context;
/* Private constructor */
private NavigationHelper(@NonNull Context context) {
this.context = context.getApplicationContext();
}
/* Call inside onCreate() in MainActivity giving as argument `this` */
@OptIn(markerClass = UnstableApi.class)
public static NavigationHelper init(@NonNull MainActivity activity) {
NavigationHelper helper = new NavigationHelper(activity);
helper.bindViews(activity);
helper.setupNavigation(activity);
return helper;
}
/* Call inside onDestroy() in MainActivity*/
public void release() {
bottomNavigationView = null;
bottomNavigationViewFrame = null;
drawerLayout = null;
navigationView = null;
navHostFragment = null;
navController = null;
}
/* Bind the views by finding them on the layout (XML id attr) */
@OptIn(markerClass = UnstableApi.class)
private void bindViews(@NonNull MainActivity activity) {
bottomNavigationView = activity.findViewById(R.id.bottom_navigation);
bottomNavigationViewFrame = activity.findViewById(R.id.bottom_navigation_frame);
drawerLayout = activity.findViewById(R.id.drawer_layout);
navigationView = activity.findViewById(R.id.nav_view);
navHostFragment = (NavHostFragment) activity
.getSupportFragmentManager()
.findFragmentById(R.id.nav_host_fragment);
navController = Objects.requireNonNull(navHostFragment).getNavController();
}
/* The navigation graph (untouched original implementation) */
@OptIn(markerClass = UnstableApi.class)
private void setupNavigation(@NonNull MainActivity activity) {
navController.addOnDestinationChangedListener(
(controller, destination, arguments) -> {
int destId = destination.getId();
boolean isTarget = destId == R.id.homeFragment ||
destId == R.id.libraryFragment ||
destId == R.id.downloadFragment;
if (isTarget &&
activity.bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED) {
activity.bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
});
NavigationUI.setupWithNavController(bottomNavigationView, navController);
NavigationUI.setupWithNavController(navigationView, navController);
}
/*
Clean public methods
Removes the need to invoke the activity on the fragment
*/
public void setBottomNavigationBarVisibility(boolean visible) {
int visibility = visible
? View.VISIBLE
: View.GONE;
bottomNavigationView.setVisibility(visibility);
bottomNavigationViewFrame.setVisibility(visibility);
}
public void setNavigationDrawerLock(boolean locked) {
int mode = locked
? DrawerLayout.LOCK_MODE_LOCKED_CLOSED
: DrawerLayout.LOCK_MODE_UNLOCKED;
drawerLayout.setDrawerLockMode(mode);
}
public void toggleNavigationDrawerLockOnOrientationChange(MainActivity activity, boolean isLandscape) {
if (Preferences.getEnableDrawerOnPortrait()) {
setNavigationDrawerLock(false);
return;
}
setNavigationDrawerLock(!isLandscape);
}
/*
All of these are the "backward compatible" changes that don't break the assumption
that everything was defined on the activity and is gobally available
*/
@NonNull
public NavController getNavController() {
return navController;
}
@NonNull
public BottomNavigationView getBottomNavigationView() {
return bottomNavigationView;
}
@NonNull
public FrameLayout getBottomNavigationViewFrame() {
return bottomNavigationViewFrame;
}
@NonNull
public DrawerLayout getDrawerLayout() {
return drawerLayout;
}
/*
Auxiliar functions, could be moved somewhere else
*/
@OptIn(markerClass = UnstableApi.class)
public void setSystemBarsVisibility(MainActivity activity, boolean visibility) {
WindowInsetsControllerCompat insetsController;
Window window = activity.getWindow();
View decorView = window.getDecorView();
insetsController = new WindowInsetsControllerCompat(window, decorView);
if (visibility) {
WindowCompat.setDecorFitsSystemWindows(window, true);
insetsController.show(WindowInsetsCompat.Type.navigationBars());
insetsController.show(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_DEFAULT);
} else {
WindowCompat.setDecorFitsSystemWindows(window, false);
insetsController.hide(WindowInsetsCompat.Type.navigationBars());
insetsController.hide(WindowInsetsCompat.Type.statusBars());
insetsController.setSystemBarsBehavior(
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
}
}