diff --git a/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationController.java b/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationController.java new file mode 100644 index 000000000..a6437dca9 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationController.java @@ -0,0 +1,44 @@ +package com.cappielloantonio.tempo.navigation; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.appcompat.app.AppCompatActivity; +import androidx.media3.common.util.UnstableApi; +import androidx.navigation.NavController; + +import com.cappielloantonio.tempo.navigation.NavigationHelper; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; + +public class NavigationController { + + NavigationHelper helper; + + public NavigationController(@NonNull NavigationHelper helper) { + this.helper = helper; + } + + public void syncWithBottomSheetBehavior(BottomSheetBehavior bottomSheetBehavior, + NavController navController) { + helper.syncWithBottomSheetBehavior(bottomSheetBehavior, navController); + + } + + public void setNavbarVisibility(boolean visibility) { + helper.setBottomNavigationBarVisibility(visibility); + } + + public void setDrawerLock(boolean visibility) { + helper.setNavigationDrawerLock(visibility); + } + + public void toggleDrawerLockOnOrientation(AppCompatActivity activity) { + helper.toggleNavigationDrawerLockOnOrientationChange(activity); + } + + public void setSystemBarsVisibility(AppCompatActivity activity, boolean visibility) { + helper.setSystemBarsVisibility(activity, visibility); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationHelper.java b/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationHelper.java new file mode 100644 index 000000000..b5916f73b --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/navigation/NavigationHelper.java @@ -0,0 +1,167 @@ +package com.cappielloantonio.tempo.navigation; + +import android.content.Context; +import android.content.res.Configuration; +import android.view.View; +import android.view.Window; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.OptIn; +import androidx.appcompat.app.AppCompatActivity; +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.NavDestination; +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 org.jetbrains.annotations.Contract; + +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; + + /* States that need to be remembered */ + // -- // + + /* Private constructor */ + public NavigationHelper(@NonNull BottomNavigationView bottomNavigationView, + @NonNull FrameLayout bottomNavigationViewFrame, + @NonNull DrawerLayout drawerLayout, + @NonNull NavigationView navigationView, + @NonNull NavHostFragment navHostFragment) { + this.bottomNavigationView = bottomNavigationView; + this.bottomNavigationViewFrame = bottomNavigationViewFrame; + this.drawerLayout = drawerLayout; + this.navigationView = navigationView; + this.navHostFragment = navHostFragment; + } + + public void syncWithBottomSheetBehavior(@NonNull BottomSheetBehavior bottomSheetBehavior, + @NonNull NavController navController) { + navController.addOnDestinationChangedListener( + (controller, destination, arguments) -> { + // React to the user clicking one of these on bottom-navbar/drawer + boolean isTarget = isTargetDestination(destination); + int currentState = bottomSheetBehavior.getState(); + + if (isTarget && currentState == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } + }); + + NavigationUI.setupWithNavController(bottomNavigationView, navController); + NavigationUI.setupWithNavController(navigationView, navController); + } + + @Contract(pure = true) + private static boolean isTargetDestination(NavDestination destination) { + int destId = destination.getId(); + return destId == R.id.homeFragment || + destId == R.id.libraryFragment || + destId == R.id.downloadFragment || + destId == R.id.albumCatalogueFragment || + destId == R.id.artistCatalogueFragment || + destId == R.id.genreCatalogueFragment || + destId == R.id.playlistCatalogueFragment; + } + + /* + 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); + } + + @OptIn(markerClass = UnstableApi.class) + public void toggleNavigationDrawerLockOnOrientationChange( + AppCompatActivity activity) { + + int orientation = activity.getResources().getConfiguration().orientation; + boolean isLandscape = orientation == Configuration.ORIENTATION_LANDSCAPE; + + 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 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(AppCompatActivity 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); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java index 3509175f4..c819aec02 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/activity/MainActivity.java @@ -3,7 +3,6 @@ import android.content.Context; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Rect; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.net.NetworkInfo; @@ -11,12 +10,12 @@ import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; -import android.util.Log; import android.view.View; -import android.view.ViewGroup; +import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.core.splashscreen.SplashScreen; +import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.FragmentManager; import androidx.lifecycle.ViewModelProvider; import androidx.media3.common.MediaItem; @@ -26,7 +25,6 @@ import androidx.media3.common.util.UnstableApi; import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; -import androidx.navigation.ui.NavigationUI; import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.BuildConfig; @@ -34,8 +32,12 @@ import com.cappielloantonio.tempo.broadcast.receiver.ConnectivityStatusBroadcastReceiver; import com.cappielloantonio.tempo.databinding.ActivityMainBinding; import com.cappielloantonio.tempo.github.utils.UpdateUtil; +import com.cappielloantonio.tempo.navigation.NavigationController; +import com.cappielloantonio.tempo.navigation.NavigationHelper; import com.cappielloantonio.tempo.service.MediaManager; import com.cappielloantonio.tempo.ui.activity.base.BaseActivity; +import com.cappielloantonio.tempo.ui.controller.BottomSheetController; +import com.cappielloantonio.tempo.ui.controller.BottomSheetHelper; import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog; import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog; import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog; @@ -60,18 +62,21 @@ public class MainActivity extends BaseActivity { public ActivityMainBinding bind; private MainViewModel mainViewModel; - private FragmentManager fragmentManager; - private NavHostFragment navHostFragment; - private BottomNavigationView bottomNavigationView; public NavController navController; - private BottomSheetBehavior bottomSheetBehavior; - private boolean isLandscape = false; + private NavigationController navigationController; + private BottomSheetController bottomSheetController; + public BottomSheetBehavior bottomSheetBehavior; + public boolean isLandscape = false; private AssetLinkNavigator assetLinkNavigator; private AssetLinkUtil.AssetLink pendingAssetLink; ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver; private Intent pendingDownloadPlaybackIntent; + public ActivityMainBinding getBinding() { + return bind; + } + @Override protected void onCreate(Bundle savedInstanceState) { SplashScreen.installSplashScreen(this); @@ -111,6 +116,7 @@ protected void onStart() { protected void onResume() { super.onResume(); pingServer(); + toggleNavigationDrawerLockOnOrientationChange(); } @Override @@ -137,7 +143,6 @@ public void onBackPressed() { } public void init() { - fragmentManager = getSupportFragmentManager(); initBottomSheet(); initNavigation(); @@ -148,59 +153,78 @@ public void init() { goToLogin(); } - // Set bottom navigation height - if (isLandscape) { - ViewGroup.LayoutParams layoutParams = bottomNavigationView.getLayoutParams(); - Rect windowRect = new Rect(); - bottomNavigationView.getWindowVisibleDisplayFrame(windowRect); - layoutParams.width = windowRect.height(); - bottomNavigationView.setLayoutParams(layoutParams); - } + toggleNavigationDrawerLockOnOrientationChange(); + + } + + private void initNavigation() { + // We link the nav_graph.xml with our navigationController + NavHostFragment navHostFragment = (NavHostFragment) this + .getSupportFragmentManager() + .findFragmentById(R.id.nav_host_fragment); + navController = Objects.requireNonNull(navHostFragment).getNavController(); + /* + navController is currently global since some legacy code still invokes it directly + the MainActivity methods that use it must be converted to NavigationHelper methods + */ + + // Helper + NavigationHelper navigationHelper = + new NavigationHelper( + findViewById(R.id.bottom_navigation), + findViewById(R.id.bottom_navigation_frame), + findViewById(R.id.drawer_layout), + findViewById(R.id.nav_view), + navHostFragment + ); + + // Controller + navigationController = new NavigationController(navigationHelper); + navigationController.syncWithBottomSheetBehavior(bottomSheetBehavior, navController); } - // BOTTOM SHEET/NAVIGATION private void initBottomSheet() { - bottomSheetBehavior = BottomSheetBehavior.from(findViewById(R.id.player_bottom_sheet)); - bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback); - fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit(); + FragmentManager fragmentManager = getSupportFragmentManager(); + View bottomSheetView = findViewById(R.id.player_bottom_sheet); + bottomSheetBehavior = BottomSheetBehavior.from(bottomSheetView); + /* + bottomSheetBehavior is currently global since some legacy code still invokes it directly + the MainActivity methods that use it must be converted to BottomSheetHelper methods + */ + + // Helper + BottomSheetHelper bottomSheetHelper = + new BottomSheetHelper( + bottomSheetBehavior, + bottomSheetView, + fragmentManager + ); - checkBottomSheetAfterStateChanged(); + // Controller + bottomSheetController = new BottomSheetController(bottomSheetHelper); + bottomSheetController.addCallback(bottomSheetCallback); + bottomSheetController.replaceFragment(R.id.player_bottom_sheet); + bottomSheetController.checkAfterStateChanged(mainViewModel); } public void setBottomSheetInPeek(Boolean isVisible) { - if (isVisible) { - bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - } else { - bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); - } + bottomSheetController.setStateInPeek(isVisible); } public void setBottomSheetVisibility(boolean visibility) { - if (visibility) { - findViewById(R.id.player_bottom_sheet).setVisibility(View.VISIBLE); - } else { - findViewById(R.id.player_bottom_sheet).setVisibility(View.GONE); - } - } - - private void checkBottomSheetAfterStateChanged() { - final Handler handler = new Handler(); - final Runnable runnable = () -> setBottomSheetInPeek(mainViewModel.isQueueLoaded()); - handler.postDelayed(runnable, 100); + bottomSheetController.setVisibility(visibility); } public void collapseBottomSheetDelayed() { - final Handler handler = new Handler(); - final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); - handler.postDelayed(runnable, 100); + bottomSheetController.collapseDelayed(); } public void expandBottomSheet() { - bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + bottomSheetController.expand(); } public void setBottomSheetDraggableState(Boolean isDraggable) { - bottomSheetBehavior.setDraggable(isDraggable); + bottomSheetController.setDraggable(isDraggable); } private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback = @@ -213,7 +237,7 @@ public void onStateChanged(@NonNull View view, int state) { switch (state) { case BottomSheetBehavior.STATE_HIDDEN: - resetMusicSession(); + resetMusicSession(); // I can't put the callback inside BottomSheetHelper because of this line break; case BottomSheetBehavior.STATE_COLLAPSED: if (playerBottomSheetFragment != null) @@ -237,12 +261,7 @@ public void onSlide(@NonNull View view, float slideOffset) { }; private void animateBottomSheet(float slideOffset) { - PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet"); - if (playerBottomSheetFragment != null) { - float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f; - playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset); - playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE); - } + bottomSheetController.animate(slideOffset); } private void animateBottomNavigation(float slideOffset, int navigationHeight) { @@ -257,36 +276,53 @@ private void animateBottomNavigation(float slideOffset, int navigationHeight) { bind.bottomNavigation.setTranslationY(slideY); } - private void initNavigation() { - bottomNavigationView = findViewById(R.id.bottom_navigation); - navHostFragment = (NavHostFragment) fragmentManager.findFragmentById(R.id.nav_host_fragment); - navController = Objects.requireNonNull(navHostFragment).getNavController(); - - /* - * 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); + public void setBottomNavigationBarVisibility(boolean visibility) { + navigationController.setNavbarVisibility(visibility); } - public void setBottomNavigationBarVisibility(boolean visibility) { - if (visibility) { - bottomNavigationView.setVisibility(View.VISIBLE); + public void toggleBottomNavigationBarVisibilityOnOrientationChange() { + float displayDensity = getResources().getDisplayMetrics().density; + // Ignore orientation change, bottom navbar always hidden + if (Preferences.getHideBottomNavbarOnPortrait()) { + navigationController.setNavbarVisibility(false); + bottomSheetController.setPeekHeight(56, displayDensity); + navigationController.setSystemBarsVisibility(this, !isLandscape); + return; + } + + if (!isLandscape) { + // Show app navbar + show system bars + bottomSheetController.setPeekHeight(136, displayDensity); + navigationController.setNavbarVisibility(true); + navigationController.setSystemBarsVisibility(this, true); } else { - bottomNavigationView.setVisibility(View.GONE); + // Hide app navbar + hide system bars + bottomSheetController.setPeekHeight(56, displayDensity); + navigationController.setNavbarVisibility(false); + navigationController.setSystemBarsVisibility(this, false); } } + public void setNavigationDrawerLock(boolean locked) { + navigationController.setDrawerLock(locked); + } + + public void toggleNavigationDrawerLockOnOrientationChange() { + navigationController.toggleDrawerLockOnOrientation(this); + } + + public void setSystemBarsVisibility(boolean visibility) { + navigationController.setSystemBarsVisibility(this, visibility); + } + + /* + There are only 4 init functions that must exist up to here + 1. init() + 2. initNavigation() + 3. initBottomSheet() + 4. bottomSheetCallback = new BottomSheetBehavior.BottomSheetCallback() { ... } + */ + private void initService() { MediaManager.check(getMediaBrowserListenableFuture()); @@ -321,7 +357,7 @@ private void goToLogin() { } private void goToHome() { - bottomNavigationView.setVisibility(View.VISIBLE); + setBottomNavigationBarVisibility(true); if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) { navController.navigate(R.id.action_landingFragment_to_homeFragment); @@ -570,4 +606,4 @@ private void playDownloadedMedia(Intent intent) { MediaManager.playDownloadedMediaItem(getMediaBrowserListenableFuture(), mediaItem); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetController.java b/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetController.java new file mode 100644 index 000000000..9ebff6d92 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetController.java @@ -0,0 +1,63 @@ +package com.cappielloantonio.tempo.ui.controller; + +import androidx.annotation.NonNull; + +import com.cappielloantonio.tempo.viewmodel.MainViewModel; +import com.google.android.material.bottomsheet.BottomSheetBehavior; + +public class BottomSheetController { + + BottomSheetHelper helper; + + public BottomSheetController(@NonNull BottomSheetHelper bottomSheetPlayerHelper) { + this.helper = bottomSheetPlayerHelper; + } + + public void expand() { + helper.setState(BottomSheetBehavior.STATE_EXPANDED); + } + + public void hide() { + helper.setState(BottomSheetBehavior.STATE_HIDDEN); + } + + public void setStateInPeek(boolean isVisible) { + helper.setStateInPeek(isVisible); + } + + public void setVisibility(boolean visibility) { + helper.setVisibility(visibility); + } + + public void addCallback(BottomSheetBehavior.BottomSheetCallback callback) { + helper.addCallback(callback); + } + + public void replaceFragment(int playerBottomSheet) { + helper.replaceFragment(playerBottomSheet); + } + + public void checkAfterStateChanged(MainViewModel mainViewModel) { + helper.checkAfterStateChanged(mainViewModel); + } + + public void collapseDelayed() { + helper.collapseDelayed(); + } + + public void setDraggable(Boolean isDraggable) { + helper.setDraggable(isDraggable); + } + + public int getState() { + return helper.getState(); + } + + public void animate(float slideOffset) { + helper.animate(slideOffset); + } + + public void setPeekHeight(int peekHeight, float displayDensity) { + helper.setPeekHeight(peekHeight, displayDensity); + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetHelper.java b/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetHelper.java new file mode 100644 index 000000000..a0c88938d --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/controller/BottomSheetHelper.java @@ -0,0 +1,97 @@ +package com.cappielloantonio.tempo.ui.controller; + +import android.os.Handler; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentManager; + +import com.cappielloantonio.tempo.R; +import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment; +import com.cappielloantonio.tempo.viewmodel.MainViewModel; +import com.google.android.material.bottomsheet.BottomSheetBehavior; + +public class BottomSheetHelper { + + BottomSheetBehavior bottomSheetBehavior; + View bottomSheetView; + FragmentManager fragmentManager; // Of the entire activity + PlayerBottomSheetFragment playerBottomSheetFragment; + + public void setState(int state) { + bottomSheetBehavior.setState(state); + } + + public BottomSheetHelper(@NonNull BottomSheetBehavior bottomSheetBehavior, + @NonNull View bottomSheetView, + @NonNull FragmentManager fragmentManager) { + this.bottomSheetBehavior = bottomSheetBehavior; + this.bottomSheetView = bottomSheetView; + this.fragmentManager = fragmentManager; + this.playerBottomSheetFragment = new PlayerBottomSheetFragment(); + } + + public void addCallback(BottomSheetBehavior.BottomSheetCallback callback) { + bottomSheetBehavior.addBottomSheetCallback(callback); + } + + public void setStateInPeek(boolean isVisible) { + if (isVisible) { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + } else { + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN); + } + } + + public void setVisibility(boolean visibility) { + if (visibility) { + bottomSheetView.setVisibility(View.VISIBLE); + } else { + bottomSheetView.setVisibility(View.GONE); + } + } + + public void replaceFragment(int playerBottomSheet) { + fragmentManager + .beginTransaction() + .replace( + playerBottomSheet, + playerBottomSheetFragment, + "PlayerBottomSheet") + .commit(); + } + + public void checkAfterStateChanged(MainViewModel mainViewModel) { + final Handler handler = new Handler(); + final Runnable runnable = () -> setStateInPeek(mainViewModel.isQueueLoaded()); + handler.postDelayed(runnable, 100); + } + + public void collapseDelayed() { + final Handler handler = new Handler(); + final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + handler.postDelayed(runnable, 100); + } + + public void setDraggable(Boolean isDraggable) { + bottomSheetBehavior.setDraggable((isDraggable)); + } + + public int getState() { + return bottomSheetBehavior.getState(); + } + + public void animate(float slideOffset) { + if (playerBottomSheetFragment != null) { + float condensedSlideOffset = Math.max(0.0f, Math.min(0.2f, slideOffset - 0.2f)) / 0.2f; + playerBottomSheetFragment.getPlayerHeader().setAlpha(1 - condensedSlideOffset); + playerBottomSheetFragment.getPlayerHeader().setVisibility(condensedSlideOffset > 0.99 ? View.GONE : View.VISIBLE); + } + } + + public void setPeekHeight(int peekHeight, float displayDensity) { + int newPeekPx = (int) (peekHeight * displayDensity); + bottomSheetBehavior.setPeekHeight(newPeekPx); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java index 12ca24349..72d1e0f30 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/DownloadFragment.java @@ -83,7 +83,7 @@ public void onStart() { super.onStart(); initializeMediaBrowser(); - activity.setBottomNavigationBarVisibility(true); + activity.toggleBottomNavigationBarVisibilityOnOrientationChange(); activity.setBottomSheetVisibility(true); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/EqualizerFragment.kt b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/EqualizerFragment.kt index ac6608c73..04aae67c5 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/EqualizerFragment.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/EqualizerFragment.kt @@ -21,18 +21,26 @@ import com.cappielloantonio.tempo.R import com.cappielloantonio.tempo.service.EqualizerManager import com.cappielloantonio.tempo.service.BaseMediaService import com.cappielloantonio.tempo.service.MediaService +import com.cappielloantonio.tempo.ui.activity.MainActivity import com.cappielloantonio.tempo.util.Preferences class EqualizerFragment : Fragment() { + private lateinit var activity: MainActivity private var equalizerManager: EqualizerManager? = null private lateinit var eqBandsContainer: LinearLayout private lateinit var eqSwitch: Switch private lateinit var resetButton: Button private lateinit var safeSpace: Space private val bandSeekBars = mutableListOf() - private var receiverRegistered = false + + @OptIn(UnstableApi::class) + override fun onAttach(context: Context) { + super.onAttach(context) + activity = requireActivity() as MainActivity + } + private val equalizerUpdatedReceiver = object : BroadcastReceiver() { @OptIn(UnstableApi::class) override fun onReceive(context: Context?, intent: Intent?) { @@ -73,6 +81,8 @@ class EqualizerFragment : Fragment() { ) receiverRegistered = true } + val showBottomBar = !Preferences.getHideBottomNavbarOnPortrait() + activity.setBottomNavigationBarVisibility(showBottomBar) } override fun onStop() { diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeFragment.java index a0d0380c3..7697c01f7 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/HomeFragment.java @@ -53,7 +53,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat public void onStart() { super.onStart(); - activity.setBottomNavigationBarVisibility(true); + activity.toggleBottomNavigationBarVisibilityOnOrientationChange(); activity.setBottomSheetVisibility(true); } diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java index b50ee60f9..daa5d4121 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/LibraryFragment.java @@ -87,7 +87,7 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat public void onStart() { super.onStart(); initializeMediaBrowser(); - activity.setBottomNavigationBarVisibility(true); + activity.toggleBottomNavigationBarVisibilityOnOrientationChange(); } @Override diff --git a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SettingsFragment.java b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SettingsFragment.java index aab615936..2981fa7c9 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/com/cappielloantonio/tempo/ui/fragment/SettingsFragment.java @@ -130,6 +130,8 @@ public void onStart() { super.onStart(); activity.setBottomNavigationBarVisibility(false); activity.setBottomSheetVisibility(false); + activity.setNavigationDrawerLock(true); + activity.setSystemBarsVisibility(!activity.isLandscape); } @Override @@ -167,6 +169,8 @@ public void onResume() { public void onStop() { super.onStop(); activity.setBottomSheetVisibility(true); + activity.toggleNavigationDrawerLockOnOrientationChange(); + activity.setSystemBarsVisibility(!activity.isLandscape); } @Override diff --git a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt index a95c84ae1..47c30cc00 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt +++ b/app/src/main/java/com/cappielloantonio/tempo/util/Preferences.kt @@ -30,6 +30,8 @@ object Preferences { private const val IMAGE_CACHE_SIZE = "image_cache_size" private const val STREAMING_CACHE_SIZE = "streaming_cache_size" private const val LANDSCAPE_ITEMS_PER_ROW = "landscape_items_per_row" + private const val ENABLE_DRAWER_ON_PORTRAIT = "enable_drawer_on_portrait" + private const val HIDE_BOTTOM_NAVBAR_ON_PORTRAIT = "hide_bottom_navbar_on_portrait" private const val IMAGE_SIZE = "image_size" private const val MAX_BITRATE_WIFI = "max_bitrate_wifi" private const val MAX_BITRATE_MOBILE = "max_bitrate_mobile" @@ -310,6 +312,16 @@ object Preferences { return App.getInstance().preferences.getString(LANDSCAPE_ITEMS_PER_ROW, "4")!!.toInt() } + @JvmStatic + fun getEnableDrawerOnPortrait(): Boolean { + return App.getInstance().preferences.getBoolean(ENABLE_DRAWER_ON_PORTRAIT, false) + } + + @JvmStatic + fun getHideBottomNavbarOnPortrait(): Boolean { + return App.getInstance().preferences.getBoolean(HIDE_BOTTOM_NAVBAR_ON_PORTRAIT, false) + } + @JvmStatic fun getImageSize(): Int { return App.getInstance().preferences.getString(IMAGE_SIZE, "-1")!!.toInt() diff --git a/app/src/main/res/drawable/ic_albums.xml b/app/src/main/res/drawable/ic_albums.xml new file mode 100644 index 000000000..4c1c697df --- /dev/null +++ b/app/src/main/res/drawable/ic_albums.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_artists.xml b/app/src/main/res/drawable/ic_artists.xml new file mode 100644 index 000000000..03d8308f8 --- /dev/null +++ b/app/src/main/res/drawable/ic_artists.xml @@ -0,0 +1,32 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_genres.xml b/app/src/main/res/drawable/ic_genres.xml new file mode 100644 index 000000000..3e7b66795 --- /dev/null +++ b/app/src/main/res/drawable/ic_genres.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/ic_playlist.xml b/app/src/main/res/drawable/ic_playlist.xml new file mode 100644 index 000000000..08225cc90 --- /dev/null +++ b/app/src/main/res/drawable/ic_playlist.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/layout-land/activity_main.xml b/app/src/main/res/layout-land/activity_main.xml index 581aa4ce7..1b6d2e9c7 100644 --- a/app/src/main/res/layout-land/activity_main.xml +++ b/app/src/main/res/layout-land/activity_main.xml @@ -1,16 +1,15 @@ - + android:background="?attr/colorSurface"> + android:layout_height="match_parent"> - - - + + - - \ No newline at end of file + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 01edf7f5f..15a8d99cd 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,16 +1,16 @@ - + android:background="?attr/colorSurface"> + + android:layout_height="match_parent"> + + + + + + - - + diff --git a/app/src/main/res/layout/nav_drawer_header.xml b/app/src/main/res/layout/nav_drawer_header.xml new file mode 100644 index 000000000..b6ff546c5 --- /dev/null +++ b/app/src/main/res/layout/nav_drawer_header.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/nav_drawer.xml b/app/src/main/res/menu/nav_drawer.xml new file mode 100644 index 000000000..a01dfcedf --- /dev/null +++ b/app/src/main/res/menu/nav_drawer.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index c6aba5abb..d234d5440 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -220,6 +220,10 @@ + If enabled, show the shuffle button, remove the heart in the mini player Show radio If enabled, show the radio section. Restart the app for it to take full effect. + Enable drawer on portrait [Experimental] + Unlocks the lateral landscape menu drawer on portrait. The changes will take effect on restart. + Hide bottom navbar on portrait [Experimental] + Experimental.Increases vertical space by removing the bottom navbar. The changes will take effect on restart. Auto download lyrics Automatically save lyrics when they are available so they can be shown while offline. Set replay gain mode diff --git a/app/src/main/res/xml/global_preferences.xml b/app/src/main/res/xml/global_preferences.xml index 4ee034e53..cdb875a9f 100644 --- a/app/src/main/res/xml/global_preferences.xml +++ b/app/src/main/res/xml/global_preferences.xml @@ -54,6 +54,16 @@ android:defaultValue="false" android:key="always_on_display" /> + + + +