Merge branch 'nav-bottom-refactor' into bleeding-edge
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
package com.cappielloantonio.tempo.navigation;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.navigation.NavController;
|
||||
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior;
|
||||
|
||||
public class NavigationController {
|
||||
|
||||
NavigationHelper helper;
|
||||
|
||||
public NavigationController(@NonNull NavigationHelper helper) {
|
||||
this.helper = helper;
|
||||
}
|
||||
|
||||
public void syncWithBottomSheetBehavior(BottomSheetBehavior<View> bottomSheetBehavior,
|
||||
NavController navController) {
|
||||
helper.syncWithBottomSheetBehavior(bottomSheetBehavior, navController);
|
||||
|
||||
}
|
||||
|
||||
public void setNavbarVisibility(boolean visibility) {
|
||||
helper.setBottomNavigationBarVisibility(visibility);
|
||||
}
|
||||
|
||||
public void setDrawerLock(boolean visibility) {
|
||||
helper.setNavigationDrawerLock(visibility);
|
||||
}
|
||||
|
||||
public boolean isNavigationDrawerLocked() {
|
||||
return helper.isNavigationDrawerLocked();
|
||||
}
|
||||
|
||||
public void toggleDrawerLockOnOrientation(AppCompatActivity activity) {
|
||||
helper.toggleNavigationDrawerLockOnOrientationChange(activity);
|
||||
}
|
||||
|
||||
public void setSystemBarsVisibility(AppCompatActivity activity, boolean visibility) {
|
||||
helper.setSystemBarsVisibility(activity, visibility);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
package com.cappielloantonio.tempo.navigation;
|
||||
|
||||
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.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;
|
||||
|
||||
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<View> 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);
|
||||
}
|
||||
|
||||
public boolean isNavigationDrawerLocked() {
|
||||
return drawerLayout.getDrawerLockMode(navigationView) != DrawerLayout.LOCK_MODE_UNLOCKED;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,23 +4,16 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
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;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
import androidx.core.view.WindowCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.core.view.WindowInsetsControllerCompat;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -31,7 +24,6 @@ import androidx.media3.common.Player;
|
||||
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;
|
||||
@@ -39,8 +31,12 @@ import com.cappielloantonio.tempo.R;
|
||||
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;
|
||||
@@ -70,10 +66,12 @@ public class MainActivity extends BaseActivity {
|
||||
private NavHostFragment navHostFragment;
|
||||
private BottomNavigationView bottomNavigationView;
|
||||
private FrameLayout bottomNavigationViewFrame;
|
||||
public NavController navController;
|
||||
private DrawerLayout drawerLayout;
|
||||
private NavigationView navigationView;
|
||||
private BottomSheetBehavior bottomSheetBehavior;
|
||||
public NavController navController;
|
||||
private NavigationController navigationController;
|
||||
private BottomSheetController bottomSheetController;
|
||||
public BottomSheetBehavior bottomSheetBehavior;
|
||||
public boolean isLandscape = false;
|
||||
private AssetLinkNavigator assetLinkNavigator;
|
||||
private AssetLinkUtil.AssetLink pendingAssetLink;
|
||||
@@ -81,6 +79,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);
|
||||
@@ -147,7 +149,6 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
public void init() {
|
||||
fragmentManager = getSupportFragmentManager();
|
||||
|
||||
initBottomSheet();
|
||||
initNavigation();
|
||||
@@ -162,49 +163,74 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
}
|
||||
|
||||
// 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();
|
||||
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
|
||||
*/
|
||||
|
||||
checkBottomSheetAfterStateChanged();
|
||||
// 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);
|
||||
}
|
||||
|
||||
private void initBottomSheet() {
|
||||
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
|
||||
);
|
||||
|
||||
// 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 =
|
||||
@@ -217,7 +243,7 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
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)
|
||||
@@ -241,12 +267,7 @@ public class MainActivity extends BaseActivity {
|
||||
};
|
||||
|
||||
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) {
|
||||
@@ -261,117 +282,56 @@ 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 ||
|
||||
destination.getId() == R.id.albumCatalogueFragment ||
|
||||
destination.getId() == R.id.artistCatalogueFragment ||
|
||||
destination.getId() == R.id.genreCatalogueFragment ||
|
||||
destination.getId() == R.id.playlistCatalogueFragment)
|
||||
) {
|
||||
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);
|
||||
}
|
||||
navigationController.setNavbarVisibility(visibility);
|
||||
}
|
||||
|
||||
public void toggleBottomNavigationBarVisibilityOnOrientationChange() {
|
||||
float displayDensity = getResources().getDisplayMetrics().density;
|
||||
// Ignore orientation change, bottom navbar always hidden
|
||||
if (Preferences.getHideBottomNavbarOnPortrait()) {
|
||||
setBottomNavigationBarVisibility(false);
|
||||
setPortraitPlayerBottomSheetPeekHeight(56);
|
||||
setSystemBarsVisibility(!isLandscape);
|
||||
navigationController.setNavbarVisibility(false);
|
||||
bottomSheetController.setPeekHeight(56, displayDensity);
|
||||
navigationController.setSystemBarsVisibility(this, !isLandscape);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isLandscape) {
|
||||
// Show app navbar + show system bars
|
||||
setPortraitPlayerBottomSheetPeekHeight(136);
|
||||
setBottomNavigationBarVisibility(true);
|
||||
setSystemBarsVisibility(true);
|
||||
bottomSheetController.setPeekHeight(136, displayDensity);
|
||||
navigationController.setNavbarVisibility(true);
|
||||
navigationController.setSystemBarsVisibility(this, true);
|
||||
} else {
|
||||
// Hide app navbar + hide system bars
|
||||
setPortraitPlayerBottomSheetPeekHeight(56);
|
||||
setBottomNavigationBarVisibility(false);
|
||||
setSystemBarsVisibility(false);
|
||||
bottomSheetController.setPeekHeight(56, displayDensity);
|
||||
navigationController.setNavbarVisibility(false);
|
||||
navigationController.setSystemBarsVisibility(this, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void setNavigationDrawerLock(boolean locked) {
|
||||
int mode = locked
|
||||
? DrawerLayout.LOCK_MODE_LOCKED_CLOSED
|
||||
: DrawerLayout.LOCK_MODE_UNLOCKED;
|
||||
drawerLayout.setDrawerLockMode(mode);
|
||||
navigationController.setDrawerLock(locked);
|
||||
}
|
||||
|
||||
public boolean isNavigationDrawerLocked() {
|
||||
return navigationController.isNavigationDrawerLocked();
|
||||
}
|
||||
|
||||
public void toggleNavigationDrawerLockOnOrientationChange() {
|
||||
// Ignore orientation check, drawer always unlocked
|
||||
if (Preferences.getEnableDrawerOnPortrait()) {
|
||||
setNavigationDrawerLock(false);
|
||||
return;
|
||||
}
|
||||
if (!isLandscape) {
|
||||
setNavigationDrawerLock(true);
|
||||
} else {
|
||||
setNavigationDrawerLock(false);
|
||||
}
|
||||
navigationController.toggleDrawerLockOnOrientation(this);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
navigationController.setSystemBarsVisibility(this, visibility);
|
||||
}
|
||||
|
||||
private void setPortraitPlayerBottomSheetPeekHeight(int peekHeight) {
|
||||
FrameLayout bottomSheet = findViewById(R.id.player_bottom_sheet);
|
||||
BottomSheetBehavior<FrameLayout> behavior =
|
||||
BottomSheetBehavior.from(bottomSheet);
|
||||
|
||||
int newPeekPx = (int) (peekHeight * getResources().getDisplayMetrics().density);
|
||||
behavior.setPeekHeight(newPeekPx);
|
||||
}
|
||||
/*
|
||||
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());
|
||||
@@ -407,7 +367,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -657,4 +617,4 @@ public class MainActivity extends BaseActivity {
|
||||
|
||||
MediaManager.playDownloadedMediaItem(getMediaBrowserListenableFuture(), mediaItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<View> bottomSheetBehavior;
|
||||
View bottomSheetView;
|
||||
FragmentManager fragmentManager; // Of the entire activity
|
||||
PlayerBottomSheetFragment playerBottomSheetFragment;
|
||||
|
||||
public void setState(int state) {
|
||||
bottomSheetBehavior.setState(state);
|
||||
}
|
||||
|
||||
public BottomSheetHelper(@NonNull BottomSheetBehavior<View> 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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,603 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SwitchPreference;
|
||||
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredArtistSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class SettingsContainerFragment extends PreferenceFragmentCompat {
|
||||
|
||||
private static final String TAG = "SettingsFragment";
|
||||
private MainActivity activity;
|
||||
|
||||
private SettingViewModel settingViewModel;
|
||||
|
||||
private ActivityResultLauncher<Intent> directoryPickerLauncher;
|
||||
|
||||
private MediaService.LocalBinder mediaServiceBinder;
|
||||
private boolean isServiceBound = false;
|
||||
private ActivityResultLauncher<Intent> equalizerResultLauncher;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
equalizerResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {}
|
||||
);
|
||||
|
||||
if (!BuildConfig.FLAVOR.equals("tempus")) {
|
||||
PreferenceCategory githubUpdateCategory = findPreference("settings_github_update_category_key");
|
||||
if (githubUpdateCategory != null) {
|
||||
getPreferenceScreen().removePreference(githubUpdateCategory);
|
||||
}
|
||||
}
|
||||
|
||||
directoryPickerLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent data = result.getData();
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
if (uri != null) {
|
||||
requireContext().getContentResolver().takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
);
|
||||
|
||||
Preferences.setDownloadDirectoryUri(uri.toString());
|
||||
ExternalAudioReader.refreshCache();
|
||||
Toast.makeText(requireContext(), R.string.settings_download_folder_set, Toast.LENGTH_SHORT).show();
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
settingViewModel = new ViewModelProvider(requireActivity()).get(SettingViewModel.class);
|
||||
|
||||
if (view != null) {
|
||||
getListView().setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.global_padding_bottom));
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
activity.setBottomNavigationBarVisibility(false);
|
||||
activity.setBottomSheetVisibility(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
checkSystemEqualizer();
|
||||
checkCacheStorage();
|
||||
checkStorage();
|
||||
checkDownloadDirectory();
|
||||
|
||||
setStreamingCacheSize();
|
||||
setAppLanguage();
|
||||
setVersion();
|
||||
setNetorkPingTimeoutBase();
|
||||
|
||||
actionLogout();
|
||||
actionScan();
|
||||
actionSyncStarredAlbums();
|
||||
actionSyncStarredTracks();
|
||||
actionSyncStarredArtists();
|
||||
actionChangeStreamingCacheStorage();
|
||||
actionChangeDownloadStorage();
|
||||
actionSetDownloadDirectory();
|
||||
actionDeleteDownloadStorage();
|
||||
actionKeepScreenOn();
|
||||
actionAutoDownloadLyrics();
|
||||
actionMiniPlayerHeart();
|
||||
|
||||
bindMediaService();
|
||||
actionAppEqualizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.global_preferences, rootKey);
|
||||
ListPreference themePreference = findPreference(Preferences.THEME);
|
||||
if (themePreference != null) {
|
||||
themePreference.setOnPreferenceChangeListener(
|
||||
(preference, newValue) -> {
|
||||
String themeOption = (String) newValue;
|
||||
ThemeHelper.applyTheme(themeOption);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSystemEqualizer() {
|
||||
Preference equalizer = findPreference("system_equalizer");
|
||||
|
||||
if (equalizer == null) return;
|
||||
|
||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||
|
||||
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
|
||||
equalizer.setOnPreferenceClickListener(preference -> {
|
||||
equalizerResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
equalizer.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCacheStorage() {
|
||||
Preference storage = findPreference("streaming_cache_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
storage.setSummary(Preferences.getStreamingCacheStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStorage() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
int pref = Preferences.getDownloadStoragePreference();
|
||||
if (pref == 0) {
|
||||
storage.setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||
} else if (pref == 1) {
|
||||
storage.setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||
} else {
|
||||
storage.setSummary(R.string.download_storage_directory_dialog_neutral_button);
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDownloadDirectory() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
Preference directory = findPreference("set_download_directory");
|
||||
|
||||
if (directory == null) return;
|
||||
|
||||
String current = Preferences.getDownloadDirectoryUri();
|
||||
if (current != null) {
|
||||
if (storage != null) storage.setVisible(false);
|
||||
directory.setVisible(true);
|
||||
directory.setIcon(R.drawable.ic_close);
|
||||
directory.setTitle(R.string.settings_clear_download_folder);
|
||||
directory.setSummary(current);
|
||||
} else {
|
||||
if (storage != null) storage.setVisible(true);
|
||||
if (Preferences.getDownloadStoragePreference() == 2) {
|
||||
directory.setVisible(true);
|
||||
directory.setIcon(R.drawable.ic_folder);
|
||||
directory.setTitle(R.string.settings_set_download_folder);
|
||||
directory.setSummary(R.string.settings_choose_download_folder);
|
||||
} else {
|
||||
directory.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setNetorkPingTimeoutBase() {
|
||||
EditTextPreference networkPingTimeoutBase = findPreference("network_ping_timeout_base");
|
||||
|
||||
if (networkPingTimeoutBase != null) {
|
||||
networkPingTimeoutBase.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
|
||||
networkPingTimeoutBase.setOnBindEditTextListener(editText -> {
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
editText.setFilters(new InputFilter[]{ (source, start, end, dest, dstart, dend) -> {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (!Character.isDigit(source.charAt(i))) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
});
|
||||
|
||||
networkPingTimeoutBase.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String input = (String) newValue;
|
||||
return input != null && !input.isEmpty();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setStreamingCacheSize() {
|
||||
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
|
||||
|
||||
if (streamingCachePreference != null) {
|
||||
streamingCachePreference.setSummaryProvider(new Preference.SummaryProvider<ListPreference>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence provideSummary(@NonNull ListPreference preference) {
|
||||
CharSequence entry = preference.getEntry();
|
||||
|
||||
if (entry == null) return null;
|
||||
|
||||
long currentSizeMb = DownloadUtil.getStreamingCacheSize(requireActivity()) / (1024 * 1024);
|
||||
|
||||
return getString(R.string.settings_summary_streaming_cache_size, entry, String.valueOf(currentSizeMb));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setAppLanguage() {
|
||||
ListPreference localePref = (ListPreference) findPreference("language");
|
||||
|
||||
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
|
||||
|
||||
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
|
||||
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
|
||||
|
||||
localePref.setEntries(entries);
|
||||
localePref.setEntryValues(entryValues);
|
||||
|
||||
String value = localePref.getValue();
|
||||
if ("default".equals(value)) {
|
||||
localePref.setSummary(requireContext().getString(R.string.settings_system_language));
|
||||
} else {
|
||||
localePref.setSummary(Locale.forLanguageTag(value).getDisplayName());
|
||||
}
|
||||
|
||||
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if ("default".equals(newValue)) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
|
||||
preference.setSummary(requireContext().getString(R.string.settings_system_language));
|
||||
} else {
|
||||
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
|
||||
AppCompatDelegate.setApplicationLocales(appLocale);
|
||||
preference.setSummary(Locale.forLanguageTag((String) newValue).getDisplayName());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setVersion() {
|
||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||
}
|
||||
|
||||
private void actionLogout() {
|
||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||
activity.quit();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionScan() {
|
||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||
settingViewModel.launchScan(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
|
||||
if (isScanning) getScanStatus();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredTracks() {
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredSyncDialog dialog = new StarredSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredAlbums() {
|
||||
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredArtists() {
|
||||
findPreference("sync_starred_artists_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredArtistSyncDialog dialog = new StarredArtistSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeStreamingCacheStorage() {
|
||||
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
|
||||
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_external_dialog_positive_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_internal_dialog_negative_button);
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeDownloadStorage() {
|
||||
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeutralClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_directory_dialog_neutral_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSetDownloadDirectory() {
|
||||
Preference pref = findPreference("set_download_directory");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
String current = Preferences.getDownloadDirectoryUri();
|
||||
|
||||
if (current != null) {
|
||||
Preferences.setDownloadDirectoryUri(null);
|
||||
Preferences.setDownloadStoragePreference(0);
|
||||
ExternalAudioReader.refreshCache();
|
||||
Toast.makeText(requireContext(), R.string.settings_download_folder_cleared, Toast.LENGTH_SHORT).show();
|
||||
checkStorage();
|
||||
checkDownloadDirectory();
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
directoryPickerLauncher.launch(intent);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void actionDeleteDownloadStorage() {
|
||||
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionMiniPlayerHeart() {
|
||||
SwitchPreference preference = findPreference("mini_shuffle_button_visibility");
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setChecked(Preferences.showShuffleInsteadOfHeart());
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
Preferences.setShuffleInsteadOfHeart((Boolean) newValue);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionAutoDownloadLyrics() {
|
||||
SwitchPreference preference = findPreference("auto_download_lyrics");
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setChecked(Preferences.isAutoDownloadLyricsEnabled());
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
Preferences.setAutoDownloadLyricsEnabled((Boolean) newValue);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void getScanStatus() {
|
||||
settingViewModel.getScanStatus(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
|
||||
if (isScanning) getScanStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void actionKeepScreenOn() {
|
||||
findPreference("always_on_display").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mediaServiceBinder = (MediaService.LocalBinder) service;
|
||||
isServiceBound = true;
|
||||
checkEqualizerBands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mediaServiceBinder = null;
|
||||
isServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
private void bindMediaService() {
|
||||
Intent intent = new Intent(requireActivity(), MediaService.class);
|
||||
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
|
||||
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
isServiceBound = true;
|
||||
}
|
||||
|
||||
private void checkEqualizerBands() {
|
||||
if (mediaServiceBinder != null) {
|
||||
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
|
||||
short numBands = eqManager.getNumberOfBands();
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setVisible(numBands > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionAppEqualizer() {
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setOnPreferenceClickListener(preference -> {
|
||||
NavController navController = NavHostFragment.findNavController(this);
|
||||
NavOptions navOptions = new NavOptions.Builder()
|
||||
.setLaunchSingleTop(true)
|
||||
.setPopUpTo(R.id.equalizerFragment, true)
|
||||
.build();
|
||||
activity.setBottomNavigationBarVisibility(true);
|
||||
activity.setBottomSheetVisibility(true);
|
||||
navController.navigate(R.id.equalizerFragment, null, navOptions);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (isServiceBound) {
|
||||
requireActivity().unbindService(serviceConnection);
|
||||
isServiceBound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +1,61 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.net.Uri;
|
||||
import static com.google.android.material.internal.ViewUtils.hideKeyboard;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.text.InputFilter;
|
||||
import android.text.InputType;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.os.LocaleListCompat;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavOptions;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.SwitchPreference;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentSettingsBinding;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredAlbumSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredArtistSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.util.ExternalAudioReader;
|
||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
private static final String TAG = "SettingsFragment";
|
||||
public class SettingsFragment extends Fragment {
|
||||
|
||||
private MainActivity activity;
|
||||
private SettingViewModel settingViewModel;
|
||||
|
||||
private ActivityResultLauncher<Intent> equalizerResultLauncher;
|
||||
private ActivityResultLauncher<Intent> directoryPickerLauncher;
|
||||
|
||||
private MediaService.LocalBinder mediaServiceBinder;
|
||||
private boolean isServiceBound = false;
|
||||
private FragmentSettingsBinding bind;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
equalizerResultLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {}
|
||||
);
|
||||
activity = (MainActivity) getActivity();
|
||||
|
||||
if (!BuildConfig.FLAVOR.equals("tempus")) {
|
||||
PreferenceCategory githubUpdateCategory = findPreference("settings_github_update_category_key");
|
||||
if (githubUpdateCategory != null) {
|
||||
getPreferenceScreen().removePreference(githubUpdateCategory);
|
||||
}
|
||||
}
|
||||
|
||||
directoryPickerLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
Intent data = result.getData();
|
||||
if (data != null) {
|
||||
Uri uri = data.getData();
|
||||
if (uri != null) {
|
||||
requireContext().getContentResolver().takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
);
|
||||
|
||||
Preferences.setDownloadDirectoryUri(uri.toString());
|
||||
ExternalAudioReader.refreshCache();
|
||||
Toast.makeText(requireContext(), R.string.settings_download_folder_set, Toast.LENGTH_SHORT).show();
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
bind = FragmentSettingsBinding.inflate(inflater,container,false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
View view = super.onCreateView(inflater, container, savedInstanceState);
|
||||
settingViewModel = new ViewModelProvider(requireActivity()).get(SettingViewModel.class);
|
||||
|
||||
if (view != null) {
|
||||
getListView().setPadding(0, 0, 0, (int) getResources().getDimension(R.dimen.global_padding_bottom));
|
||||
}
|
||||
initAppBar();
|
||||
|
||||
return view;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view,
|
||||
@Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
// Add the PreferenceFragment only the first time
|
||||
if (savedInstanceState == null) {
|
||||
SettingsContainerFragment prefFragment = new SettingsContainerFragment();
|
||||
|
||||
// Use the child fragment manager so the PreferenceFragment is scoped to this fragment
|
||||
getChildFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings_container, prefFragment)
|
||||
.setReorderingAllowed(true) // optional but recommended
|
||||
.commit();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -134,479 +67,21 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
activity.setSystemBarsVisibility(!activity.isLandscape);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
checkSystemEqualizer();
|
||||
checkCacheStorage();
|
||||
checkStorage();
|
||||
checkDownloadDirectory();
|
||||
|
||||
setStreamingCacheSize();
|
||||
setAppLanguage();
|
||||
setVersion();
|
||||
setNetorkPingTimeoutBase();
|
||||
|
||||
actionLogout();
|
||||
actionScan();
|
||||
actionSyncStarredAlbums();
|
||||
actionSyncStarredTracks();
|
||||
actionSyncStarredArtists();
|
||||
actionChangeStreamingCacheStorage();
|
||||
actionChangeDownloadStorage();
|
||||
actionSetDownloadDirectory();
|
||||
actionDeleteDownloadStorage();
|
||||
actionKeepScreenOn();
|
||||
actionAutoDownloadLyrics();
|
||||
actionMiniPlayerHeart();
|
||||
|
||||
bindMediaService();
|
||||
actionAppEqualizer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
activity.setBottomSheetVisibility(true);
|
||||
activity.toggleNavigationDrawerLockOnOrientationChange();
|
||||
activity.setSystemBarsVisibility(!activity.isLandscape);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.global_preferences, rootKey);
|
||||
ListPreference themePreference = findPreference(Preferences.THEME);
|
||||
if (themePreference != null) {
|
||||
themePreference.setOnPreferenceChangeListener(
|
||||
(preference, newValue) -> {
|
||||
String themeOption = (String) newValue;
|
||||
ThemeHelper.applyTheme(themeOption);
|
||||
return true;
|
||||
});
|
||||
if (activity.isLandscape) {
|
||||
activity.setNavigationDrawerLock(false);
|
||||
} else if (Preferences.getEnableDrawerOnPortrait()) {
|
||||
activity.setNavigationDrawerLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkSystemEqualizer() {
|
||||
Preference equalizer = findPreference("system_equalizer");
|
||||
|
||||
if (equalizer == null) return;
|
||||
|
||||
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
|
||||
|
||||
if ((intent.resolveActivity(requireActivity().getPackageManager()) != null)) {
|
||||
equalizer.setOnPreferenceClickListener(preference -> {
|
||||
equalizerResultLauncher.launch(intent);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
equalizer.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCacheStorage() {
|
||||
Preference storage = findPreference("streaming_cache_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
storage.setSummary(Preferences.getStreamingCacheStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStorage() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
int pref = Preferences.getDownloadStoragePreference();
|
||||
if (pref == 0) {
|
||||
storage.setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||
} else if (pref == 1) {
|
||||
storage.setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||
} else {
|
||||
storage.setSummary(R.string.download_storage_directory_dialog_neutral_button);
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkDownloadDirectory() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
Preference directory = findPreference("set_download_directory");
|
||||
|
||||
if (directory == null) return;
|
||||
|
||||
String current = Preferences.getDownloadDirectoryUri();
|
||||
if (current != null) {
|
||||
if (storage != null) storage.setVisible(false);
|
||||
directory.setVisible(true);
|
||||
directory.setIcon(R.drawable.ic_close);
|
||||
directory.setTitle(R.string.settings_clear_download_folder);
|
||||
directory.setSummary(current);
|
||||
} else {
|
||||
if (storage != null) storage.setVisible(true);
|
||||
if (Preferences.getDownloadStoragePreference() == 2) {
|
||||
directory.setVisible(true);
|
||||
directory.setIcon(R.drawable.ic_folder);
|
||||
directory.setTitle(R.string.settings_set_download_folder);
|
||||
directory.setSummary(R.string.settings_choose_download_folder);
|
||||
} else {
|
||||
directory.setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void setNetorkPingTimeoutBase() {
|
||||
EditTextPreference networkPingTimeoutBase = findPreference("network_ping_timeout_base");
|
||||
|
||||
if (networkPingTimeoutBase != null) {
|
||||
networkPingTimeoutBase.setSummaryProvider(EditTextPreference.SimpleSummaryProvider.getInstance());
|
||||
networkPingTimeoutBase.setOnBindEditTextListener(editText -> {
|
||||
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
|
||||
editText.setFilters(new InputFilter[]{ (source, start, end, dest, dstart, dend) -> {
|
||||
for (int i = start; i < end; i++) {
|
||||
if (!Character.isDigit(source.charAt(i))) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
private void initAppBar() {
|
||||
bind.settingsToolbar.setNavigationOnClickListener(v -> {
|
||||
activity.navController.navigateUp();
|
||||
});
|
||||
|
||||
networkPingTimeoutBase.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
String input = (String) newValue;
|
||||
return input != null && !input.isEmpty();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setStreamingCacheSize() {
|
||||
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
|
||||
|
||||
if (streamingCachePreference != null) {
|
||||
streamingCachePreference.setSummaryProvider(new Preference.SummaryProvider<ListPreference>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence provideSummary(@NonNull ListPreference preference) {
|
||||
CharSequence entry = preference.getEntry();
|
||||
|
||||
if (entry == null) return null;
|
||||
|
||||
long currentSizeMb = DownloadUtil.getStreamingCacheSize(requireActivity()) / (1024 * 1024);
|
||||
|
||||
return getString(R.string.settings_summary_streaming_cache_size, entry, String.valueOf(currentSizeMb));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setAppLanguage() {
|
||||
ListPreference localePref = (ListPreference) findPreference("language");
|
||||
|
||||
Map<String, String> locales = UIUtil.getLangPreferenceDropdownEntries(requireContext());
|
||||
|
||||
CharSequence[] entries = locales.keySet().toArray(new CharSequence[locales.size()]);
|
||||
CharSequence[] entryValues = locales.values().toArray(new CharSequence[locales.size()]);
|
||||
|
||||
localePref.setEntries(entries);
|
||||
localePref.setEntryValues(entryValues);
|
||||
|
||||
String value = localePref.getValue();
|
||||
if ("default".equals(value)) {
|
||||
localePref.setSummary(requireContext().getString(R.string.settings_system_language));
|
||||
} else {
|
||||
localePref.setSummary(Locale.forLanguageTag(value).getDisplayName());
|
||||
}
|
||||
|
||||
localePref.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if ("default".equals(newValue)) {
|
||||
AppCompatDelegate.setApplicationLocales(LocaleListCompat.getEmptyLocaleList());
|
||||
preference.setSummary(requireContext().getString(R.string.settings_system_language));
|
||||
} else {
|
||||
LocaleListCompat appLocale = LocaleListCompat.forLanguageTags((String) newValue);
|
||||
AppCompatDelegate.setApplicationLocales(appLocale);
|
||||
preference.setSummary(Locale.forLanguageTag((String) newValue).getDisplayName());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void setVersion() {
|
||||
findPreference("version").setSummary(BuildConfig.VERSION_NAME);
|
||||
}
|
||||
|
||||
private void actionLogout() {
|
||||
findPreference("logout").setOnPreferenceClickListener(preference -> {
|
||||
activity.quit();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionScan() {
|
||||
findPreference("scan_library").setOnPreferenceClickListener(preference -> {
|
||||
settingViewModel.launchScan(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
|
||||
if (isScanning) getScanStatus();
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredTracks() {
|
||||
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredSyncDialog dialog = new StarredSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredAlbums() {
|
||||
findPreference("sync_starred_albums_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredAlbumSyncDialog dialog = new StarredAlbumSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSyncStarredArtists() {
|
||||
findPreference("sync_starred_artists_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
StarredArtistSyncDialog dialog = new StarredArtistSyncDialog(() -> {
|
||||
((SwitchPreference)preference).setChecked(false);
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeStreamingCacheStorage() {
|
||||
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
|
||||
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_external_dialog_positive_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_internal_dialog_negative_button);
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeDownloadStorage() {
|
||||
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_external_dialog_positive_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_internal_dialog_negative_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNeutralClick() {
|
||||
findPreference("download_storage").setSummary(R.string.download_storage_directory_dialog_neutral_button);
|
||||
checkDownloadDirectory();
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionSetDownloadDirectory() {
|
||||
Preference pref = findPreference("set_download_directory");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceClickListener(preference -> {
|
||||
String current = Preferences.getDownloadDirectoryUri();
|
||||
|
||||
if (current != null) {
|
||||
Preferences.setDownloadDirectoryUri(null);
|
||||
Preferences.setDownloadStoragePreference(0);
|
||||
ExternalAudioReader.refreshCache();
|
||||
Toast.makeText(requireContext(), R.string.settings_download_folder_cleared, Toast.LENGTH_SHORT).show();
|
||||
checkStorage();
|
||||
checkDownloadDirectory();
|
||||
} else {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
directoryPickerLauncher.launch(intent);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void actionDeleteDownloadStorage() {
|
||||
findPreference("delete_download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DeleteDownloadStorageDialog dialog = new DeleteDownloadStorageDialog();
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionMiniPlayerHeart() {
|
||||
SwitchPreference preference = findPreference("mini_shuffle_button_visibility");
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setChecked(Preferences.showShuffleInsteadOfHeart());
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
Preferences.setShuffleInsteadOfHeart((Boolean) newValue);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionAutoDownloadLyrics() {
|
||||
SwitchPreference preference = findPreference("auto_download_lyrics");
|
||||
if (preference == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
preference.setChecked(Preferences.isAutoDownloadLyricsEnabled());
|
||||
preference.setOnPreferenceChangeListener((pref, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
Preferences.setAutoDownloadLyricsEnabled((Boolean) newValue);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void getScanStatus() {
|
||||
settingViewModel.getScanStatus(new ScanCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
findPreference("scan_library").setSummary(exception.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
findPreference("scan_library").setSummary(getString(R.string.settings_scan_result, count));
|
||||
if (isScanning) getScanStatus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void actionKeepScreenOn() {
|
||||
findPreference("always_on_display").setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if (newValue instanceof Boolean) {
|
||||
if ((Boolean) newValue) {
|
||||
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
} else {
|
||||
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private final ServiceConnection serviceConnection = new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
mediaServiceBinder = (MediaService.LocalBinder) service;
|
||||
isServiceBound = true;
|
||||
checkEqualizerBands();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
mediaServiceBinder = null;
|
||||
isServiceBound = false;
|
||||
}
|
||||
};
|
||||
|
||||
private void bindMediaService() {
|
||||
Intent intent = new Intent(requireActivity(), MediaService.class);
|
||||
intent.setAction(MediaService.ACTION_BIND_EQUALIZER);
|
||||
requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
|
||||
isServiceBound = true;
|
||||
}
|
||||
|
||||
private void checkEqualizerBands() {
|
||||
if (mediaServiceBinder != null) {
|
||||
EqualizerManager eqManager = mediaServiceBinder.getEqualizerManager();
|
||||
short numBands = eqManager.getNumberOfBands();
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setVisible(numBands > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void actionAppEqualizer() {
|
||||
Preference appEqualizer = findPreference("app_equalizer");
|
||||
if (appEqualizer != null) {
|
||||
appEqualizer.setOnPreferenceClickListener(preference -> {
|
||||
NavController navController = NavHostFragment.findNavController(this);
|
||||
NavOptions navOptions = new NavOptions.Builder()
|
||||
.setLaunchSingleTop(true)
|
||||
.setPopUpTo(R.id.equalizerFragment, true)
|
||||
.build();
|
||||
activity.setBottomNavigationBarVisibility(true);
|
||||
activity.setBottomSheetVisibility(true);
|
||||
navController.navigate(R.id.equalizerFragment, null, navOptions);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
if (isServiceBound) {
|
||||
requireActivity().unbindService(serviceConnection);
|
||||
isServiceBound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom" />
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/settings_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -415,8 +415,8 @@
|
||||
<string name="settings_show_mini_shuffle_button_summary">If enabled, show the shuffle button, remove the heart in the mini player</string>
|
||||
<string name="settings_radio">Show radio</string>
|
||||
<string name="settings_radio_summary">If enabled, show the radio section. Restart the app for it to take full effect.</string>
|
||||
<string name="settings_enable_drawer_on_landscape">Enable drawer on portrait [Experimental]</string>
|
||||
<string name="settings_enable_drawer_on_landscape_summary">Unlocks the lateral landscape menu drawer on portrait. The changes will take effect on restart.</string>
|
||||
<string name="settings_enable_drawer_on_landscape">Enable drawer on portrait</string>
|
||||
<string name="settings_enable_drawer_on_landscape_summary">Unlocks the lateral landscape menu drawer on portrait.</string>
|
||||
<string name="settings_hide_bottom_navbar_on_portrait">Hide bottom navbar on portrait [Experimental]</string>
|
||||
<string name="settings_hide_bottom_navbar_on_portrait_summary">Experimental.Increases vertical space by removing the bottom navbar. The changes will take effect on restart.</string>
|
||||
<string name="settings_auto_download_lyrics">Auto download lyrics</string>
|
||||
|
||||
Reference in New Issue
Block a user