build: change of package name

This commit is contained in:
antonio 2023-06-17 15:30:23 +02:00
parent 49afdbe4eb
commit b76a38cb30
274 changed files with 1981 additions and 2161 deletions

View file

@ -0,0 +1,342 @@
package com.cappielloantonio.tempo.ui.activity;
import android.content.Context;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.splashscreen.SplashScreen;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.ViewModelProvider;
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.R;
import com.cappielloantonio.tempo.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
import com.cappielloantonio.tempo.databinding.ActivityMainBinding;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.ui.activity.base.BaseActivity;
import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.MainViewModel;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
@UnstableApi
public class MainActivity extends BaseActivity {
private static final String TAG = "MainActivity";
public ActivityMainBinding bind;
private MainViewModel mainViewModel;
private FragmentManager fragmentManager;
private NavHostFragment navHostFragment;
private BottomNavigationView bottomNavigationView;
public NavController navController;
private BottomSheetBehavior bottomSheetBehavior;
ConnectivityStatusBroadcastReceiver connectivityStatusBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
SplashScreen.installSplashScreen(this);
super.onCreate(savedInstanceState);
bind = ActivityMainBinding.inflate(getLayoutInflater());
View view = bind.getRoot();
setContentView(view);
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
connectivityStatusReceiverManager(true);
init();
checkConnectionType();
}
@Override
protected void onStart() {
super.onStart();
initService();
}
@Override
protected void onResume() {
super.onResume();
pingServer();
}
@Override
protected void onDestroy() {
super.onDestroy();
connectivityStatusReceiverManager(false);
bind = null;
}
@Override
public void onBackPressed() {
if (bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_EXPANDED)
collapseBottomSheet();
else
super.onBackPressed();
}
public void init() {
fragmentManager = getSupportFragmentManager();
initBottomSheet();
initNavigation();
if (Preferences.getPassword() != null || (Preferences.getToken() != null && Preferences.getSalt() != null)) {
goFromLogin();
} else {
goToLogin();
}
}
// 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();
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
}
public void setBottomSheetInPeek(Boolean isVisible) {
if (isVisible) {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
} else {
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
}
}
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);
}
}
public void collapseBottomSheet() {
final Handler handler = new Handler();
final Runnable runnable = () -> bottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
handler.postDelayed(runnable, 100);
}
public void setBottomSheetDraggableState(Boolean isDraggable) {
bottomSheetBehavior.setDraggable(isDraggable);
}
private final BottomSheetBehavior.BottomSheetCallback bottomSheetCallback =
new BottomSheetBehavior.BottomSheetCallback() {
int navigationHeight;
@Override
public void onStateChanged(@NonNull View view, int state) {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
switch (state) {
case BottomSheetBehavior.STATE_HIDDEN:
hideMusicSession();
break;
case BottomSheetBehavior.STATE_COLLAPSED:
if (playerBottomSheetFragment != null)
playerBottomSheetFragment.goBackToFirstPage();
break;
case BottomSheetBehavior.STATE_SETTLING:
case BottomSheetBehavior.STATE_EXPANDED:
case BottomSheetBehavior.STATE_DRAGGING:
case BottomSheetBehavior.STATE_HALF_EXPANDED:
break;
}
}
@Override
public void onSlide(@NonNull View view, float slideOffset) {
animateBottomSheet(slideOffset);
animateBottomNavigation(slideOffset, navigationHeight);
}
};
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);
}
}
private void animateBottomNavigation(float slideOffset, int navigationHeight) {
if (slideOffset < 0) return;
if (navigationHeight == 0) {
navigationHeight = bind.bottomNavigation.getHeight();
}
float slideY = navigationHeight - navigationHeight * (1 - slideOffset);
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) {
if (visibility) {
bottomNavigationView.setVisibility(View.VISIBLE);
} else {
bottomNavigationView.setVisibility(View.GONE);
}
}
private void initService() {
MediaManager.check(getMediaBrowserListenableFuture());
getMediaBrowserListenableFuture().addListener(() -> {
try {
getMediaBrowserListenableFuture().get().addListener(new Player.Listener() {
@Override
public void onIsPlayingChanged(boolean isPlaying) {
if (isPlaying && bottomSheetBehavior.getState() == BottomSheetBehavior.STATE_HIDDEN) {
setBottomSheetInPeek(true);
}
}
});
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void goToLogin() {
setBottomNavigationBarVisibility(false);
setBottomSheetVisibility(false);
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_loginFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.settingsFragment) {
navController.navigate(R.id.action_settingsFragment_to_loginFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.homeFragment) {
navController.navigate(R.id.action_homeFragment_to_loginFragment);
}
}
private void goToHome() {
bottomNavigationView.setVisibility(View.VISIBLE);
if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) {
navController.navigate(R.id.action_landingFragment_to_homeFragment);
} else if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.loginFragment) {
navController.navigate(R.id.action_loginFragment_to_homeFragment);
}
}
public void goFromLogin() {
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
goToHome();
}
public void quit() {
resetUserSession();
resetMusicSession();
resetViewModel();
goToLogin();
}
private void resetUserSession() {
Preferences.setServerId(null);
Preferences.setSalt(null);
Preferences.setToken(null);
Preferences.setPassword(null);
Preferences.setServer(null);
Preferences.setUser(null);
// TODO Enter all settings to be reset
Preferences.setServerId(null);
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
Preferences.setSkipSilenceMode(false);
Preferences.setDataSavingMode(false);
Preferences.setStarredSyncEnabled(false);
}
private void resetMusicSession() {
MediaManager.reset(getMediaBrowserListenableFuture());
}
private void hideMusicSession() {
MediaManager.hide(getMediaBrowserListenableFuture());
}
private void resetViewModel() {
this.getViewModelStore().clear();
}
// CONNECTION
private void connectivityStatusReceiverManager(boolean isActive) {
if (isActive) {
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(connectivityStatusBroadcastReceiver, filter);
} else {
unregisterReceiver(connectivityStatusBroadcastReceiver);
}
}
private void pingServer() {
if (Preferences.getToken() != null) {
mainViewModel.ping().observe(this, isPingSuccessfull -> {
if (!isPingSuccessfull && Preferences.showServerUnreachableDialog()) {
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
dialog.show(getSupportFragmentManager(), null);
}
});
}
}
private void checkConnectionType() {
if (Preferences.isWifiOnly()) {
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivityManager.getActiveNetworkInfo().getType() != ConnectivityManager.TYPE_WIFI) {
ConnectionAlertDialog dialog = new ConnectionAlertDialog();
dialog.show(getSupportFragmentManager(), null);
}
}
}
}

View file

@ -0,0 +1,126 @@
package com.cappielloantonio.tempo.ui.activity.base;
import android.Manifest;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.exoplayer.offline.DownloadService;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.service.DownloaderService;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.util.UIUtil;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class BaseActivity extends AppCompatActivity {
private static final String TAG = "BaseActivity";
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeCastContext();
initializeDownloader();
}
@Override
protected void onStart() {
super.onStart();
setNavigationBarColor();
initializeBrowser();
}
@Override
protected void onResume() {
super.onResume();
checkBatteryOptimization();
checkPermission();
}
@Override
protected void onStop() {
releaseBrowser();
super.onStop();
}
private void checkBatteryOptimization() {
if (detectBatteryOptimization()) {
showBatteryOptimizationDialog();
}
}
private void checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.POST_NOTIFICATIONS}, 101);
}
}
}
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
return !powerManager.isIgnoringBatteryOptimizations(packageName);
}
private void showBatteryOptimizationDialog() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.activity_battery_optimizations_summary)
.setTitle(R.string.activity_battery_optimizations_title)
.setNegativeButton(R.string.activity_negative_button, null)
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
.show();
}
private void openPowerSettings() {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
startActivity(intent);
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(this, new SessionToken(this, new ComponentName(this, MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
public ListenableFuture<MediaBrowser> getMediaBrowserListenableFuture() {
return mediaBrowserListenableFuture;
}
private void initializeDownloader() {
try {
DownloadService.start(this, DownloaderService.class);
} catch (IllegalStateException e) {
DownloadService.startForeground(this, DownloaderService.class);
}
}
private void initializeCastContext() {
if (UIUtil.isCastApiAvailable(this)) CastContext.getSharedInstance(this);
}
private void setNavigationBarColor() {
getWindow().setNavigationBarColor(SurfaceColors.getColorForElevation(this, 10));
}
}

View file

@ -0,0 +1,95 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder> {
private final ClickCallback click;
private List<AlbumID3> albums;
public AlbumAdapter(ClickCallback click) {
this.click = click;
this.albums = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryAlbumBinding view = ItemLibraryAlbumBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AlbumID3 album = albums.get(position);
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.build()
.into(holder.item.albumCoverImageView);
}
@Override
public int getItemCount() {
return albums.size();
}
public AlbumID3 getItem(int position) {
return albums.get(position);
}
public void setItems(List<AlbumID3> albums) {
this.albums = albums;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryAlbumBinding item;
ViewHolder(ItemLibraryAlbumBinding item) {
super(item.getRoot());
this.item = item;
item.albumNameLabel.setSelected(true);
item.artistNameLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,95 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryArtistPageOrSimilarAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class AlbumArtistPageOrSimilarAdapter extends RecyclerView.Adapter<AlbumArtistPageOrSimilarAdapter.ViewHolder> {
private final ClickCallback click;
private List<AlbumID3> albums;
public AlbumArtistPageOrSimilarAdapter(ClickCallback click) {
this.click = click;
this.albums = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryArtistPageOrSimilarAlbumBinding view = ItemLibraryArtistPageOrSimilarAlbumBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AlbumID3 album = albums.get(position);
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.build()
.into(holder.item.artistPageAlbumCoverImageView);
}
@Override
public int getItemCount() {
return albums.size();
}
public AlbumID3 getItem(int position) {
return albums.get(position);
}
public void setItems(List<AlbumID3> albums) {
this.albums = albums;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryArtistPageOrSimilarAlbumBinding item;
ViewHolder(ItemLibraryArtistPageOrSimilarAlbumBinding item) {
super(item.getRoot());
this.item = item;
item.albumNameLabel.setSelected(true);
item.artistNameLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,165 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<AlbumID3> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(albumsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (AlbumID3 item : albumsFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
albums.clear();
albums.addAll((List) results.values);
notifyDataSetChanged();
}
};
private List<AlbumID3> albums;
private List<AlbumID3> albumsFull;
public AlbumCatalogueAdapter(ClickCallback click) {
this.click = click;
this.albums = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryCatalogueAlbumBinding view = ItemLibraryCatalogueAlbumBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AlbumID3 album = albums.get(position);
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.build()
.into(holder.item.albumCatalogueCoverImageView);
}
@Override
public int getItemCount() {
return albums.size();
}
public AlbumID3 getItem(int position) {
return albums.get(position);
}
public void setItems(List<AlbumID3> albums) {
this.albums = albums;
this.albumsFull = new ArrayList<>(albums);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Filter getFilter() {
return filtering;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryCatalogueAlbumBinding item;
ViewHolder(ItemLibraryCatalogueAlbumBinding item) {
super(item.getRoot());
this.item = item;
item.albumNameLabel.setSelected(true);
item.artistNameLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.ALBUM_ORDER_BY_NAME:
albums.sort(Comparator.comparing(AlbumID3::getName));
break;
case Constants.ALBUM_ORDER_BY_ARTIST:
albums.sort(Comparator.comparing(AlbumID3::getArtist));
break;
case Constants.ALBUM_ORDER_BY_YEAR:
albums.sort(Comparator.comparing(AlbumID3::getYear));
break;
case Constants.ALBUM_ORDER_BY_RANDOM:
Collections.shuffle(albums);
break;
}
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,98 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean isOffline;
private List<AlbumID3> albums;
public AlbumHorizontalAdapter(ClickCallback click, boolean isOffline) {
this.click = click;
this.isOffline = isOffline;
this.albums = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalAlbumBinding view = ItemHorizontalAlbumBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
AlbumID3 album = albums.get(position);
holder.item.albumTitleTextView.setText(MusicUtil.getReadableString(album.getName()));
holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.build()
.into(holder.item.albumCoverImageView);
}
@Override
public int getItemCount() {
return albums.size();
}
public void setItems(List<AlbumID3> albums) {
this.albums = albums;
notifyDataSetChanged();
}
public AlbumID3 getItem(int id) {
return albums.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalAlbumBinding item;
ViewHolder(ItemHorizontalAlbumBinding item) {
super(item.getRoot());
this.item = item;
item.albumTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.albumMoreButton.setOnClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, albums.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,111 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean mix;
private final boolean bestOf;
private List<ArtistID3> artists;
public ArtistAdapter(ClickCallback click, Boolean mix, Boolean bestOf) {
this.click = click;
this.mix = mix;
this.bestOf = bestOf;
this.artists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryArtistBinding view = ItemLibraryArtistBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ArtistID3 artist = artists.get(position);
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.build()
.into(holder.item.artistCoverImageView);
}
@Override
public int getItemCount() {
return artists.size();
}
public ArtistID3 getItem(int position) {
return artists.get(position);
}
public void setItems(List<ArtistID3> artists) {
this.artists = artists;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryArtistBinding item;
ViewHolder(ItemLibraryArtistBinding item) {
super(item.getRoot());
this.item = item;
item.artistNameLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
bundle.putBoolean(Constants.MEDIA_MIX, mix);
bundle.putBoolean(Constants.MEDIA_BEST_OF, bestOf);
click.onArtistClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,158 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogueAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<ArtistID3> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(artistFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (ArtistID3 item : artistFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
artists.clear();
artists.addAll((List) results.values);
notifyDataSetChanged();
}
};
private List<ArtistID3> artists;
private List<ArtistID3> artistFull;
public ArtistCatalogueAdapter(ClickCallback click) {
this.click = click;
this.artists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryCatalogueArtistBinding view = ItemLibraryCatalogueArtistBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ArtistID3 artist = artists.get(position);
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.build()
.into(holder.item.artistCatalogueCoverImageView);
}
@Override
public int getItemCount() {
return artists.size();
}
public ArtistID3 getItem(int position) {
return artists.get(position);
}
public void setItems(List<ArtistID3> artists) {
this.artists = artists;
this.artistFull = new ArrayList<>(artists);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Filter getFilter() {
return filtering;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryCatalogueArtistBinding item;
ViewHolder(ItemLibraryCatalogueArtistBinding item) {
super(item.getRoot());
this.item = item;
item.artistNameLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistLongClick(bundle);
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.ARTIST_ORDER_BY_NAME:
artists.sort(Comparator.comparing(ArtistID3::getName));
break;
case Constants.ARTIST_ORDER_BY_RANDOM:
Collections.shuffle(artists);
break;
}
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,112 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<ArtistID3> artists;
public ArtistHorizontalAdapter(ClickCallback click) {
this.click = click;
this.artists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalArtistBinding view = ItemHorizontalArtistBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
ArtistID3 artist = artists.get(position);
holder.item.artistNameTextView.setText(MusicUtil.getReadableString(artist.getName()));
if (artist.getAlbumCount() > 0) {
holder.item.artistInfoTextView.setText("Album count: " + artist.getAlbumCount());
} else {
holder.item.artistInfoTextView.setVisibility(View.GONE);
}
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.build()
.into(holder.item.artistCoverImageView);
}
@Override
public int getItemCount() {
return artists.size();
}
public void setItems(List<ArtistID3> artists) {
this.artists = artists;
notifyDataSetChanged();
}
public ArtistID3 getItem(int id) {
return artists.get(id);
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalArtistBinding item;
ViewHolder(ItemHorizontalArtistBinding item) {
super(item.getRoot());
this.item = item;
item.artistNameTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.artistMoreButton.setOnClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,103 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibrarySimilarArtistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.SimilarArtistID3;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class ArtistSimilarAdapter extends RecyclerView.Adapter<ArtistSimilarAdapter.ViewHolder> {
private final ClickCallback click;
private List<SimilarArtistID3> artists;
public ArtistSimilarAdapter(ClickCallback click) {
this.click = click;
this.artists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibrarySimilarArtistBinding view = ItemLibrarySimilarArtistBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
SimilarArtistID3 artist = artists.get(position);
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.build()
.into(holder.item.similarArtistCoverImageView);
}
@Override
public int getItemCount() {
return artists.size();
}
public SimilarArtistID3 getItem(int position) {
return artists.get(position);
}
public void setItems(List<SimilarArtistID3> artists) {
this.artists = artists;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibrarySimilarArtistBinding item;
ViewHolder(ItemLibrarySimilarArtistBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.artistNameLabel.setSelected(true);
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artists.get(getBindingAdapterPosition()));
click.onArtistLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,96 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.view.animation.AccelerateDecelerateInterpolator;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeDiscoverSongBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapter.ViewHolder> {
private final ClickCallback click;
private List<Child> songs;
public DiscoverSongAdapter(ClickCallback click) {
this.click = click;
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeDiscoverSongBinding view = ItemHomeDiscoverSongBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.titleDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.build()
.into(holder.item.discoverSongCoverImageView);
}
@Override
public void onViewAttachedToWindow(@NonNull ViewHolder holder) {
super.onViewAttachedToWindow(holder);
startAnimation(holder);
}
@Override
public int getItemCount() {
return songs.size();
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeDiscoverSongBinding item;
ViewHolder(ItemHomeDiscoverSongBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
bundle.putBoolean(Constants.MEDIA_MIX, true);
click.onMediaClick(bundle);
}
}
private void startAnimation(ViewHolder holder) {
holder.item.discoverSongCoverImageView.animate()
.setDuration(20000)
.setStartDelay(10)
.setInterpolator(new AccelerateDecelerateInterpolator())
.scaleX(1.4f)
.scaleY(1.4f)
.start();
}
}

View file

@ -0,0 +1,115 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@UnstableApi
public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<Child> songs;
public DownloadHorizontalAdapter(ClickCallback click) {
this.click = click;
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalDownloadBinding view = ItemHorizontalDownloadBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.downloadedSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.downloadedSongArtistTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
holder.item.downloadedSongAlbumTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
if (position > 0 && songs.get(position - 1) != null && !Objects.equals(songs.get(position - 1).getAlbum(), songs.get(position).getAlbum())) {
holder.item.divider.setPadding(0, (int) holder.itemView.getContext().getResources().getDimension(R.dimen.downloaded_item_padding), 0, 0);
} else {
if (position > 0) holder.item.divider.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
return songs.size();
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public Child getItem(int id) {
return songs.get(id);
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalDownloadBinding item;
ViewHolder(ItemHorizontalDownloadBinding item) {
super(item.getRoot());
this.item = item;
item.downloadedSongTitleTextView.setSelected(true);
item.downloadedSongArtistTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.downloadedSongMoreButton.setOnClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,76 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryGenreBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder> {
private final ClickCallback click;
private List<Genre> genres;
public GenreAdapter(ClickCallback click) {
this.click = click;
this.genres = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryGenreBinding view = ItemLibraryGenreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Genre genre = genres.get(position);
holder.item.genreLabel.setText(MusicUtil.getReadableString(genre.getGenre()));
}
@Override
public int getItemCount() {
return genres.size();
}
public Genre getItem(int position) {
return genres.get(position);
}
public void setItems(List<Genre> genres) {
this.genres = genres;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryGenreBinding item;
ViewHolder(ItemLibraryGenreBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_GENRE, Constants.MEDIA_BY_GENRE);
bundle.putParcelable(Constants.GENRE_OBJECT, genres.get(getBindingAdapterPosition()));
click.onGenreClick(bundle);
}
}
}

View file

@ -0,0 +1,129 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueGenreBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<Genre> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(genresFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (Genre item : genresFull) {
if (item.getGenre().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
genres.clear();
genres.addAll((List) results.values);
notifyDataSetChanged();
}
};
private List<Genre> genres;
private List<Genre> genresFull;
public GenreCatalogueAdapter(ClickCallback click) {
this.click = click;
this.genres = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryCatalogueGenreBinding view = ItemLibraryCatalogueGenreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Genre genre = genres.get(position);
holder.item.genreLabel.setText(MusicUtil.getReadableString(genre.getGenre()));
}
@Override
public int getItemCount() {
return genres.size();
}
public Genre getItem(int position) {
return genres.get(position);
}
public void setItems(List<Genre> genres) {
this.genres = genres;
this.genresFull = new ArrayList<>(genres);
notifyDataSetChanged();
}
@Override
public Filter getFilter() {
return filtering;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryCatalogueGenreBinding item;
ViewHolder(ItemLibraryCatalogueGenreBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_GENRE, Constants.MEDIA_BY_GENRE);
bundle.putParcelable(Constants.GENRE_OBJECT, genres.get(getBindingAdapterPosition()));
click.onGenreClick(bundle);
});
}
}
public void sort(String order) {
switch (order) {
case Constants.GENRE_ORDER_BY_NAME:
genres.sort(Comparator.comparing(Genre::getGenre));
break;
case Constants.GENRE_ORDER_BY_RANDOM:
Collections.shuffle(genres);
break;
}
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,81 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeGridTrackBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.util.Constants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class GridTrackAdapter extends RecyclerView.Adapter<GridTrackAdapter.ViewHolder> {
private final ClickCallback click;
private List<Chronology> items;
public GridTrackAdapter(ClickCallback click) {
this.click = click;
this.items = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeGridTrackBinding view = ItemHomeGridTrackBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Chronology item = items.get(position);
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), item.getCoverArtId())
.build()
.into(holder.item.trackCoverImageView);
}
@Override
public int getItemCount() {
return items.size();
}
public Chronology getItem(int position) {
return items.get(position);
}
public void setItems(List<Chronology> items) {
this.items = items;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeGridTrackBinding item;
ViewHolder(ItemHomeGridTrackBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(items));
bundle.putBoolean(Constants.MEDIA_CHRONOLOGY, true);
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle);
}
}
}

View file

@ -0,0 +1,98 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeInternetRadioStationBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class InternetRadioStationAdapter extends RecyclerView.Adapter<InternetRadioStationAdapter.ViewHolder> {
private final ClickCallback click;
private List<InternetRadioStation> internetRadioStations;
public InternetRadioStationAdapter(ClickCallback click) {
this.click = click;
this.internetRadioStations = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeInternetRadioStationBinding view = ItemHomeInternetRadioStationBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
InternetRadioStation internetRadioStation = internetRadioStations.get(position);
holder.item.internetRadioStationTitleTextView.setText(internetRadioStation.getName());
holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl())
.build()
.into(holder.item.internetRadioStationCoverImageView);
}
@Override
public int getItemCount() {
return internetRadioStations.size();
}
public void setItems(List<InternetRadioStation> internetRadioStations) {
this.internetRadioStations = internetRadioStations;
notifyDataSetChanged();
}
public InternetRadioStation getItem(int position) {
return internetRadioStations.get(position);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeInternetRadioStationBinding item;
ViewHolder(ItemHomeInternetRadioStationBinding item) {
super(item.getRoot());
this.item = item;
item.internetRadioStationTitleTextView.setSelected(true);
item.internetRadioStationSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.internetRadioStationMoreButton.setOnClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.INTERNET_RADIO_STATION_OBJECT, internetRadioStations.get(getBindingAdapterPosition()));
click.onInternetRadioStationClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.INTERNET_RADIO_STATION_OBJECT, internetRadioStations.get(getBindingAdapterPosition()));
click.onInternetRadioStationLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,88 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicDirectoryBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAdapter.ViewHolder> {
private final ClickCallback click;
private List<Child> children;
public MusicDirectoryAdapter(ClickCallback click) {
this.click = click;
this.children = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryMusicDirectoryBinding view = ItemLibraryMusicDirectoryBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child child = children.get(position);
holder.item.musicDirectoryTitleTextView.setText(child.getTitle());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), child.getCoverArtId())
.build()
.into(holder.item.musicDirectoryCoverImageView);
}
@Override
public int getItemCount() {
return children.size();
}
public void setItems(List<Child> children) {
this.children = children != null ? children : Collections.emptyList();
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryMusicDirectoryBinding item;
ViewHolder(ItemLibraryMusicDirectoryBinding item) {
super(item.getRoot());
this.item = item;
item.musicDirectoryTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
if (children.get(getBindingAdapterPosition()).isDir()) {
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
click.onMusicDirectoryClick(bundle);
} else {
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(children));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle);
}
}
}
}

View file

@ -0,0 +1,85 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicFolderBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class MusicFolderAdapter extends RecyclerView.Adapter<MusicFolderAdapter.ViewHolder> {
private final ClickCallback click;
private List<MusicFolder> musicFolders;
public MusicFolderAdapter(ClickCallback click) {
this.click = click;
this.musicFolders = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryMusicFolderBinding view = ItemLibraryMusicFolderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
MusicFolder musicFolder = musicFolders.get(position);
holder.item.musicFolderTitleTextView.setText(musicFolder.getName());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), musicFolder.getName())
.build()
.into(holder.item.musicFolderCoverImageView);
}
@Override
public int getItemCount() {
return musicFolders.size();
}
public void setItems(List<MusicFolder> musicFolders) {
this.musicFolders = musicFolders;
notifyDataSetChanged();
}
public MusicFolder getItem(int position) {
return musicFolders.get(position);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryMusicFolderBinding item;
ViewHolder(ItemLibraryMusicFolderBinding item) {
super(item.getRoot());
this.item = item;
item.musicFolderTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
item.musicFolderMoreButton.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.MUSIC_FOLDER_OBJECT, musicFolders.get(getBindingAdapterPosition()));
click.onMusicFolderClick(bundle);
}
}
}

View file

@ -0,0 +1,80 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.ViewHolder> {
private final ClickCallback click;
private List<Artist> artists;
public MusicIndexAdapter(ClickCallback click) {
this.click = click;
this.artists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLibraryMusicIndexBinding view = ItemLibraryMusicIndexBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Artist artist = artists.get(position);
holder.item.musicIndexTitleTextView.setText(artist.getName());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getName())
.build()
.into(holder.item.musicIndexCoverImageView);
}
@Override
public int getItemCount() {
return artists.size();
}
public void setItems(List<Artist> artists) {
this.artists = artists;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLibraryMusicIndexBinding item;
ViewHolder(ItemLibraryMusicIndexBinding item) {
super(item.getRoot());
this.item = item;
item.musicIndexTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
item.musicIndexMoreButton.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
click.onMusicIndexClick(bundle);
}
}
}

View file

@ -0,0 +1,118 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.session.MediaBrowser;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemPlayerQueueSongBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueueAdapter.ViewHolder> {
private final ClickCallback click;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private List<Child> songs;
public PlayerSongQueueAdapter(ClickCallback click) {
this.click = click;
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemPlayerQueueSongBinding view = ItemPlayerQueueSongBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.queueSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.queueSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.build()
.into(holder.item.queueSongCoverImageView);
MediaManager.getCurrentIndex(mediaBrowserListenableFuture, index -> {
if (position < index) {
holder.item.queueSongTitleTextView.setAlpha(0.2f);
holder.item.queueSongSubtitleTextView.setAlpha(0.2f);
} else {
holder.item.queueSongTitleTextView.setAlpha(1.0f);
holder.item.queueSongSubtitleTextView.setAlpha(1.0f);
}
});
}
public List<Child> getItems() {
return this.songs;
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
@Override
public int getItemCount() {
if (songs == null) {
return 0;
}
return songs.size();
}
@Override
public long getItemId(int position) {
return position;
}
public void setMediaBrowserListenableFuture(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
this.mediaBrowserListenableFuture = mediaBrowserListenableFuture;
}
public Child getItem(int id) {
return songs.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemPlayerQueueSongBinding item;
ViewHolder(ItemPlayerQueueSongBinding item) {
super(item.getRoot());
this.item = item;
item.queueSongTitleTextView.setSelected(true);
item.queueSongSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(songs));
bundle.putInt(Constants.ITEM_POSITION, getBindingAdapterPosition());
click.onMediaClick(bundle);
}
}
}

View file

@ -0,0 +1,79 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalPlaylistDialogBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class PlaylistDialogHorizontalAdapter extends RecyclerView.Adapter<PlaylistDialogHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<Playlist> playlists;
public PlaylistDialogHorizontalAdapter(ClickCallback click) {
this.click = click;
this.playlists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalPlaylistDialogBinding view = ItemHorizontalPlaylistDialogBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Playlist playlist = playlists.get(position);
holder.item.playlistDialogTitleTextView.setText(MusicUtil.getReadableString(playlist.getName()));
holder.item.playlistDialogCountTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
}
@Override
public int getItemCount() {
return playlists.size();
}
public void setItems(List<Playlist> playlists) {
this.playlists = playlists;
notifyDataSetChanged();
}
public Playlist getItem(int id) {
return playlists.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalPlaylistDialogBinding item;
ViewHolder(ItemHorizontalPlaylistDialogBinding item) {
super(item.getRoot());
this.item = item;
item.playlistDialogTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PLAYLIST_OBJECT, playlists.get(getBindingAdapterPosition()));
click.onPlaylistClick(bundle);
}
}
}

View file

@ -0,0 +1,74 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalPlaylistDialogTrackBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class PlaylistDialogSongHorizontalAdapter extends RecyclerView.Adapter<PlaylistDialogSongHorizontalAdapter.ViewHolder> {
private List<Child> songs;
public PlaylistDialogSongHorizontalAdapter() {
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalPlaylistDialogTrackBinding view = ItemHorizontalPlaylistDialogTrackBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.playlistDialogSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.playlistDialogAlbumArtistTextView.setText(MusicUtil.getReadableString(song.getArtist()));
holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.build()
.into(holder.item.playlistDialogSongCoverImageView);
}
@Override
public int getItemCount() {
return songs.size();
}
public List<Child> getItems() {
return this.songs;
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public Child getItem(int id) {
return songs.get(id);
}
public static class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalPlaylistDialogTrackBinding item;
ViewHolder(ItemHorizontalPlaylistDialogTrackBinding item) {
super(item.getRoot());
this.item = item;
item.playlistDialogSongTitleTextView.setSelected(true);
}
}
}

View file

@ -0,0 +1,151 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalPlaylistBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHorizontalAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private List<Playlist> playlists;
private List<Playlist> playlistsFull;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<Playlist> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(playlistsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (Playlist item : playlistsFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
playlists.clear();
playlists.addAll((List) results.values);
notifyDataSetChanged();
}
};
public PlaylistHorizontalAdapter(ClickCallback click) {
this.click = click;
this.playlists = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalPlaylistBinding view = ItemHorizontalPlaylistBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Playlist playlist = playlists.get(position);
holder.item.playlistTitleTextView.setText(MusicUtil.getReadableString(playlist.getName()));
holder.item.playlistSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), playlist.getCoverArtId())
.build()
.into(holder.item.playlistCoverImageView);
}
@Override
public int getItemCount() {
return playlists.size();
}
public Playlist getItem(int id) {
return playlists.get(id);
}
public void setItems(List<Playlist> playlists) {
this.playlists = playlists;
this.playlistsFull = new ArrayList<>(playlists);
notifyDataSetChanged();
}
@Override
public Filter getFilter() {
return filtering;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalPlaylistBinding item;
ViewHolder(ItemHorizontalPlaylistBinding item) {
super(item.getRoot());
this.item = item;
item.playlistTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.playlistMoreButton.setOnClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PLAYLIST_OBJECT, playlists.get(getBindingAdapterPosition()));
click.onPlaylistClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PLAYLIST_OBJECT, playlists.get(getBindingAdapterPosition()));
click.onPlaylistLongClick(bundle);
return true;
}
}
public void sort(String order) {
switch (order) {
case Constants.PLAYLIST_ORDER_BY_NAME:
playlists.sort(Comparator.comparing(Playlist::getName));
break;
case Constants.PLAYLIST_ORDER_BY_RANDOM:
Collections.shuffle(playlists);
break;
}
notifyDataSetChanged();
}
}

View file

@ -0,0 +1,144 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeCataloguePodcastChannelBinding;
import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueAlbumBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<PodcastChannelCatalogueAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
List<PodcastChannel> filteredList = new ArrayList<>();
if (constraint == null || constraint.length() == 0) {
filteredList.addAll(podcastChannelsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
for (PodcastChannel item : podcastChannelsFull) {
if (item.getTitle().toLowerCase().contains(filterPattern)) {
filteredList.add(item);
}
}
}
FilterResults results = new FilterResults();
results.values = filteredList;
return results;
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
podcastChannels.clear();
podcastChannels.addAll((List) results.values);
notifyDataSetChanged();
}
};
private List<PodcastChannel> podcastChannels;
private List<PodcastChannel> podcastChannelsFull;
public PodcastChannelCatalogueAdapter(ClickCallback click) {
this.click = click;
this.podcastChannels = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeCataloguePodcastChannelBinding view = ItemHomeCataloguePodcastChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
PodcastChannel podcastChannel = podcastChannels.get(position);
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId())
.build()
.into(holder.item.podcastChannelCatalogueCoverImageView);
}
@Override
public int getItemCount() {
return podcastChannels.size();
}
public PodcastChannel getItem(int position) {
return podcastChannels.get(position);
}
public void setItems(List<PodcastChannel> podcastChannels) {
this.podcastChannels = podcastChannels;
this.podcastChannelsFull = new ArrayList<>(podcastChannels);
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public Filter getFilter() {
return filtering;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeCataloguePodcastChannelBinding item;
ViewHolder(ItemHomeCataloguePodcastChannelBinding item) {
super(item.getRoot());
this.item = item;
item.podcastChannelTitleLabel.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onAlbumLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,97 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalPodcastChannelBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<PodcastChannelHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<PodcastChannel> podcastChannels;
public PodcastChannelHorizontalAdapter(ClickCallback click) {
this.click = click;
this.podcastChannels = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalPodcastChannelBinding view = ItemHorizontalPodcastChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
PodcastChannel podcastChannel = podcastChannels.get(position);
holder.item.podcastChannelTitleTextView.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl())
.build()
.into(holder.item.podcastChannelCoverImageView);
}
@Override
public int getItemCount() {
return podcastChannels.size();
}
public void setItems(List<PodcastChannel> podcastChannels) {
this.podcastChannels = podcastChannels;
notifyDataSetChanged();
}
public PodcastChannel getItem(int id) {
return podcastChannels.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalPodcastChannelBinding item;
ViewHolder(ItemHorizontalPodcastChannelBinding item) {
super(item.getRoot());
this.item = item;
item.podcastChannelTitleTextView.setSelected(true);
item.podcastChannelDescriptionTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.podcastChannelMoreButton.setOnClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onPodcastChannelClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_CHANNEL_OBJECT, podcastChannels.get(getBindingAdapterPosition()));
click.onPodcastChannelLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,106 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHomePodcastEpisodeBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.List;
public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAdapter.ViewHolder> {
private final ClickCallback click;
private List<PodcastEpisode> podcastEpisodes;
public PodcastEpisodeAdapter(ClickCallback click) {
this.click = click;
this.podcastEpisodes = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomePodcastEpisodeBinding view = ItemHomePodcastEpisodeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
PodcastEpisode podcastEpisode = podcastEpisodes.get(position);
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM d");
holder.item.podcastTitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getTitle()));
holder.item.podcastSubtitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getArtist()));
holder.item.podcastReleasesAndDurationLabel.setText(holder.itemView.getContext().getString(R.string.podcast_release_date_duration_formatter, simpleDateFormat.format(podcastEpisode.getPublishDate()), MusicUtil.getReadablePodcastDurationString(podcastEpisode.getDuration())));
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
.build()
.into(holder.item.podcastCoverImageView);
}
@Override
public int getItemCount() {
return podcastEpisodes.size();
}
public void setItems(List<PodcastEpisode> podcastEpisodes) {
this.podcastEpisodes = podcastEpisodes;
notifyDataSetChanged();
}
@Override
public int getItemViewType(int position) {
return position;
}
@Override
public long getItemId(int position) {
return position;
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomePodcastEpisodeBinding item;
ViewHolder(ItemHomePodcastEpisodeBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> openMore());
item.podcastPlayButton.setOnClickListener(v -> onClick());
item.podcastMoreButton.setOnClickListener(v -> openMore());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeClick(bundle);
}
private boolean openMore() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.PODCAST_OBJECT, podcastEpisodes.get(getBindingAdapterPosition()));
click.onPodcastEpisodeLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,86 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLoginServerBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Server;
import java.util.ArrayList;
import java.util.List;
public class ServerAdapter extends RecyclerView.Adapter<ServerAdapter.ViewHolder> {
private final ClickCallback click;
private List<Server> servers;
public ServerAdapter(ClickCallback click) {
this.click = click;
this.servers = new ArrayList<>();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemLoginServerBinding view = ItemLoginServerBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Server server = servers.get(position);
holder.item.serverNameTextView.setText(server.getServerName());
holder.item.serverAddressTextView.setText(server.getAddress());
}
@Override
public int getItemCount() {
return servers.size();
}
public void setItems(List<Server> servers) {
this.servers = servers;
notifyDataSetChanged();
}
public Server getItem(int id) {
return servers.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemLoginServerBinding item;
ViewHolder(ItemLoginServerBinding item) {
super(item.getRoot());
this.item = item;
item.serverNameTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable("server_object", servers.get(getBindingAdapterPosition()));
click.onServerClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable("server_object", servers.get(getBindingAdapterPosition()));
click.onServerLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,92 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeSimilarTrackBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class SimilarTrackAdapter extends RecyclerView.Adapter<SimilarTrackAdapter.ViewHolder> {
private final ClickCallback click;
private List<Child> songs;
public SimilarTrackAdapter(ClickCallback click) {
this.click = click;
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeSimilarTrackBinding view = ItemHomeSimilarTrackBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.build()
.into(holder.item.trackCoverImageView);
}
@Override
public int getItemCount() {
return songs.size();
}
public Child getItem(int position) {
return songs.get(position);
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeSimilarTrackBinding item;
ViewHolder(ItemHomeSimilarTrackBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
bundle.putBoolean(Constants.MEDIA_MIX, true);
click.onMediaClick(bundle);
}
public boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,121 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean isCoverVisible;
private List<Child> songs;
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) {
this.click = click;
this.isCoverVisible = isCoverVisible;
this.songs = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalTrackBinding view = ItemHorizontalTrackBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Child song = songs.get(position);
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
holder.item.trackNumberTextView.setText(String.valueOf(song.getTrack()));
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
} else {
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
}
if (isCoverVisible) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.build()
.into(holder.item.songCoverImageView);
if (isCoverVisible) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
if (!isCoverVisible) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
if (!isCoverVisible && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
}
}
@Override
public int getItemCount() {
return songs.size();
}
public void setItems(List<Child> songs) {
this.songs = songs;
notifyDataSetChanged();
}
public Child getItem(int id) {
return songs.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalTrackBinding item;
ViewHolder(ItemHorizontalTrackBinding item) {
super(item.getRoot());
this.item = item;
item.searchResultSongTitleTextView.setSelected(true);
item.searchResultSongSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.searchResultSongMoreButton.setOnClickListener(v -> onLongClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(Constants.TRACKS_OBJECT, new ArrayList<>(MusicUtil.limitPlayableMedia(songs, getBindingAdapterPosition())));
bundle.putInt(Constants.ITEM_POSITION, MusicUtil.getPlayableMediaPosition(getBindingAdapterPosition()));
click.onMediaClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, songs.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
}
}
}

View file

@ -0,0 +1,74 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHomeYearBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.util.Constants;
import java.util.Collections;
import java.util.List;
public class YearAdapter extends RecyclerView.Adapter<YearAdapter.ViewHolder> {
private final ClickCallback click;
private List<Integer> years;
public YearAdapter(ClickCallback click) {
this.click = click;
this.years = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHomeYearBinding view = ItemHomeYearBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
int year = years.get(position);
holder.item.yearLabel.setText(Integer.toString(year));
}
@Override
public int getItemCount() {
return years.size();
}
public Integer getItem(int position) {
return years.get(position);
}
public void setItems(List<Integer> years) {
this.years = years;
notifyDataSetChanged();
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHomeYearBinding item;
ViewHolder(ItemHomeYearBinding item) {
super(item.getRoot());
this.item = item;
itemView.setOnClickListener(v -> onClick());
}
public void onClick() {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_YEAR, Constants.MEDIA_BY_YEAR);
bundle.putInt("year_object", years.get(getBindingAdapterPosition()));
click.onYearClick(bundle);
}
}
}

View file

@ -0,0 +1,60 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogConnectionAlertBinding;
import com.cappielloantonio.tempo.util.Preferences;
import java.util.Objects;
public class ConnectionAlertDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogConnectionAlertBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.connection_alert_dialog_title)
.setPositiveButton(R.string.connection_alert_dialog_positive_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.connection_alert_dialog_negative_button, (dialog, id) -> dialog.cancel());
if (!Preferences.isDataSavingMode()) {
builder.setNeutralButton(R.string.connection_alert_dialog_neutral_button, (dialog, id) -> {
});
}
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Preferences.setDataSavingMode(true);
Objects.requireNonNull(getDialog()).dismiss();
});
}
}

View file

@ -0,0 +1,105 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistChooserBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaylistChooserViewModel;
import java.util.Objects;
public class PlaylistChooserDialog extends DialogFragment implements ClickCallback {
private DialogPlaylistChooserBinding bind;
private PlaylistChooserViewModel playlistChooserViewModel;
private PlaylistDialogHorizontalAdapter playlistDialogHorizontalAdapter;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistChooserBinding.inflate(getLayoutInflater());
playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.playlist_chooser_dialog_title)
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> {
})
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public void onStart() {
super.onStart();
initPlaylistView();
setSongInfo();
setButtonAction();
}
private void setSongInfo() {
playlistChooserViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT));
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
Objects.requireNonNull(getDialog()).dismiss();
});
}
private void initPlaylistView() {
bind.playlistDialogRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistDialogRecyclerView.setHasFixedSize(true);
playlistDialogHorizontalAdapter = new PlaylistDialogHorizontalAdapter(this);
bind.playlistDialogRecyclerView.setAdapter(playlistDialogHorizontalAdapter);
playlistChooserViewModel.getPlaylistList(requireActivity()).observe(requireActivity(), playlists -> {
if (playlists != null) {
if (playlists.size() > 0) {
if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.GONE);
if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.VISIBLE);
playlistDialogHorizontalAdapter.setItems(playlists);
} else {
if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.VISIBLE);
if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.GONE);
}
}
});
}
@Override
public void onPlaylistClick(Bundle bundle) {
Playlist playlist = bundle.getParcelable(Constants.PLAYLIST_OBJECT);
playlistChooserViewModel.addSongToPlaylist(playlist.getId());
dismiss();
}
}

View file

@ -0,0 +1,160 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel;
import java.util.Collections;
import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel;
private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistEditorBinding.inflate(getLayoutInflater());
playlistEditorViewModel = new ViewModelProvider(requireActivity()).get(PlaylistEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.playlist_editor_dialog_title)
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> {
})
.setNeutralButton(R.string.playlist_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setParameterInfo();
setButtonAction();
initSongsView();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setParameterInfo() {
if (requireArguments().getParcelable(Constants.TRACK_OBJECT) != null) {
playlistEditorViewModel.setSongToAdd(requireArguments().getParcelable(Constants.TRACK_OBJECT));
playlistEditorViewModel.setPlaylistToEdit(null);
} else if (requireArguments().getParcelable(Constants.PLAYLIST_OBJECT) != null) {
playlistEditorViewModel.setSongToAdd(null);
playlistEditorViewModel.setPlaylistToEdit(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
if (playlistEditorViewModel.getPlaylistToEdit() != null) {
bind.playlistNameTextView.setText(MusicUtil.getReadableString(playlistEditorViewModel.getPlaylistToEdit().getName()));
}
}
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (playlistEditorViewModel.getSongToAdd() != null) {
playlistEditorViewModel.createPlaylist(playlistName);
} else if (playlistEditorViewModel.getPlaylistToEdit() != null) {
playlistEditorViewModel.updatePlaylist(playlistName);
}
Objects.requireNonNull(getDialog()).dismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
playlistEditorViewModel.deletePlaylist();
Objects.requireNonNull(getDialog()).dismiss();
});
}
private void initSongsView() {
bind.playlistSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistSongRecyclerView.setHasFixedSize(true);
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs));
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
int fromPosition = -1;
int toPosition = -1;
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
if (originalPosition == -1)
originalPosition = viewHolder.getBindingAdapterPosition();
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
Collections.swap(playlistDialogSongHorizontalAdapter.getItems(), fromPosition, toPosition);
Objects.requireNonNull(recyclerView.getAdapter()).notifyItemMoved(fromPosition, toPosition);
return false;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
/*
* Qui vado a riscivere tutta la table Queue, quando teoricamente potrei solo swappare l'ordine degli elementi interessati
* Nel caso la coda contenesse parecchi brani, potrebbero verificarsi rallentamenti pesanti
*/
playlistEditorViewModel.orderPlaylistSongLiveListAfterSwap(playlistDialogSongHorizontalAdapter.getItems());
originalPosition = -1;
fromPosition = -1;
toPosition = -1;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
playlistEditorViewModel.removeFromPlaylistSongLiveList(viewHolder.getBindingAdapterPosition());
Objects.requireNonNull(bind.playlistSongRecyclerView.getAdapter()).notifyItemRemoved(viewHolder.getBindingAdapterPosition());
}
}
).attachToRecyclerView(bind.playlistSongRecyclerView);
}
private boolean validateInput() {
playlistName = Objects.requireNonNull(bind.playlistNameTextView.getText()).toString().trim();
if (TextUtils.isEmpty(playlistName)) {
bind.playlistNameTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
}

View file

@ -0,0 +1,84 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPodcastChannelEditorBinding;
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelEditorViewModel;
import java.util.Objects;
public class PodcastChannelEditorDialog extends DialogFragment {
private DialogPodcastChannelEditorBinding bind;
private PodcastChannelEditorViewModel podcastChannelEditorViewModel;
private PodcastCallback podcastCallback;
private String channelUrl;
public PodcastChannelEditorDialog(PodcastCallback podcastCallback) {
this.podcastCallback = podcastCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPodcastChannelEditorBinding.inflate(getLayoutInflater());
podcastChannelEditorViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.podcast_channel_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
podcastChannelEditorViewModel.createChannel(channelUrl);
dismissDialog();
}
});
}
private boolean validateInput() {
channelUrl = Objects.requireNonNull(bind.podcastChannelRssUrlNameTextView.getText()).toString().trim();
if (TextUtils.isEmpty(channelUrl)) {
bind.podcastChannelRssUrlNameTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
private void dismissDialog() {
podcastCallback.onDismiss();
Objects.requireNonNull(getDialog()).dismiss();
}
}

View file

@ -0,0 +1,119 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogRadioEditorBinding;
import com.cappielloantonio.tempo.interfaces.RadioCallback;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RadioEditorViewModel;
import java.util.Objects;
public class RadioEditorDialog extends DialogFragment {
private DialogRadioEditorBinding bind;
private RadioEditorViewModel radioEditorViewModel;
private RadioCallback radioCallback;
private String radioName;
private String radioStreamURL;
private String radioHomepageURL;
public RadioEditorDialog(RadioCallback radioCallback) {
this.radioCallback = radioCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogRadioEditorBinding.inflate(getLayoutInflater());
radioEditorViewModel = new ViewModelProvider(requireActivity()).get(RadioEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.radio_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
})
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setParameterInfo();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setParameterInfo() {
if (getArguments() != null && getArguments().getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT) != null) {
InternetRadioStation toEdit = requireArguments().getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT);
radioEditorViewModel.setRadioToEdit(toEdit);
bind.internetRadioStationNameTextView.setText(toEdit.getName());
bind.internetRadioStationStreamUrlTextView.setText(toEdit.getStreamUrl());
bind.internetRadioStationHomepageUrlTextView.setText(toEdit.getHomePageUrl());
}
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (radioEditorViewModel.getRadioToEdit() == null) {
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
} else {
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
}
dismissDialog();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
radioEditorViewModel.deleteRadio();
dismissDialog();
});
}
private boolean validateInput() {
radioName = Objects.requireNonNull(bind.internetRadioStationNameTextView.getText()).toString().trim();
radioStreamURL = Objects.requireNonNull(bind.internetRadioStationStreamUrlTextView.getText()).toString().trim();
radioHomepageURL = Objects.requireNonNull(bind.internetRadioStationHomepageUrlTextView.getText()).toString().trim();
if (TextUtils.isEmpty(radioName)) {
bind.internetRadioStationNameTextView.setError(getString(R.string.error_required));
return false;
}
if (TextUtils.isEmpty(radioStreamURL)) {
bind.internetRadioStationStreamUrlTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
private void dismissDialog() {
radioCallback.onDismiss();
Objects.requireNonNull(getDialog()).dismiss();
}
}

View file

@ -0,0 +1,73 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogRatingBinding;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
public class RatingDialog extends DialogFragment {
private static final String TAG = "ServerSignupDialog";
private DialogRatingBinding bind;
private RatingViewModel ratingViewModel;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogRatingBinding.inflate(getLayoutInflater());
ratingViewModel = new ViewModelProvider(requireActivity()).get(RatingViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.rating_dialog_title)
.setNegativeButton(R.string.rating_dialog_negative_button, (dialog, id) -> dialog.cancel())
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()));
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setElementInfo();
setRating();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setElementInfo() {
if (requireArguments().getParcelable(Constants.TRACK_OBJECT) != null) {
ratingViewModel.setSong(requireArguments().getParcelable(Constants.TRACK_OBJECT));
} else if (requireArguments().getParcelable(Constants.ALBUM_OBJECT) != null) {
ratingViewModel.setAlbum(requireArguments().getParcelable(Constants.ALBUM_OBJECT));
} else if (requireArguments().getParcelable(Constants.ARTIST_OBJECT) != null) {
ratingViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
}
}
private void setRating() {
if (ratingViewModel.getSong() != null) {
ratingViewModel.getLiveSong().observe(this, song -> {
bind.ratingBar.setRating(song.getUserRating() != null ? song.getUserRating() : 0);
});
} else if (ratingViewModel.getAlbum() != null) {
ratingViewModel.getLiveAlbum().observe(this, album -> bind.ratingBar.setRating(/*album.getRating()*/ 0));
} else if (ratingViewModel.getArtist() != null) {
ratingViewModel.getLiveArtist().observe(this, artist -> bind.ratingBar.setRating(/*artist.getRating()*/ 0));
}
}
}

View file

@ -0,0 +1,126 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogServerSignupBinding;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
import java.util.Objects;
import java.util.UUID;
public class ServerSignupDialog extends DialogFragment {
private static final String TAG = "ServerSignupDialog";
private DialogServerSignupBinding bind;
private LoginViewModel loginViewModel;
private String serverName;
private String username;
private String password;
private String server;
private boolean lowSecurity = false;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
bind = DialogServerSignupBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.server_signup_dialog_title)
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> {
})
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
}
@Override
public void onStart() {
super.onStart();
setServerInfo();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setServerInfo() {
if (getArguments() != null) {
loginViewModel.setServerToEdit(requireArguments().getParcelable("server_object"));
if (loginViewModel.getServerToEdit() != null) {
bind.serverNameTextView.setText(loginViewModel.getServerToEdit().getServerName());
bind.usernameTextView.setText(loginViewModel.getServerToEdit().getUsername());
bind.passwordTextView.setText("");
bind.serverTextView.setText(loginViewModel.getServerToEdit().getAddress());
bind.lowSecurityCheckbox.setChecked(loginViewModel.getServerToEdit().isLowSecurity());
}
} else {
loginViewModel.setServerToEdit(null);
}
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
saveServerPreference();
Objects.requireNonNull(getDialog()).dismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
loginViewModel.deleteServer(null);
Objects.requireNonNull(getDialog()).dismiss();
});
}
private boolean validateInput() {
serverName = Objects.requireNonNull(bind.serverNameTextView.getText()).toString().trim();
username = Objects.requireNonNull(bind.usernameTextView.getText()).toString().trim();
password = bind.lowSecurityCheckbox.isChecked() ? MusicUtil.passwordHexEncoding(Objects.requireNonNull(bind.passwordTextView.getText()).toString()) : Objects.requireNonNull(bind.passwordTextView.getText()).toString();
server = Objects.requireNonNull(bind.serverTextView.getText()).toString().trim();
lowSecurity = bind.lowSecurityCheckbox.isChecked();
if (TextUtils.isEmpty(serverName)) {
bind.serverNameTextView.setError(getString(R.string.error_required));
return false;
}
if (TextUtils.isEmpty(username)) {
bind.usernameTextView.setError(getString(R.string.error_required));
return false;
}
if (TextUtils.isEmpty(server)) {
bind.serverTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
private void saveServerPreference() {
String serverID = loginViewModel.getServerToEdit() != null ? loginViewModel.getServerToEdit().getServerId() : UUID.randomUUID().toString();
loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.password, this.server, System.currentTimeMillis(), this.lowSecurity));
}
}

View file

@ -0,0 +1,70 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogServerUnreachableBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Preferences;
public class ServerUnreachableDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogServerUnreachableBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogServerUnreachableBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.server_unreachable_dialog_title)
.setPositiveButton(R.string.server_unreachable_dialog_positive_button, null)
.setNeutralButton(R.string.server_unreachable_dialog_neutral_button, null)
.setNegativeButton(R.string.server_unreachable_dialog_negative_button, (dialog, id) -> dialog.cancel());
AlertDialog popup = builder.create();
popup.setCancelable(false);
popup.setCanceledOnTouchOutside(false);
return popup;
}
@Override
public void onStart() {
super.onStart();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = (AlertDialog) getDialog();
if(dialog != null) {
(dialog).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
MainActivity activity = (MainActivity) getActivity();
if (activity != null) activity.quit();
dialog.dismiss();
});
(dialog).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
dialog.dismiss();
});
}
}
}

View file

@ -0,0 +1,89 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogStarredSyncBinding;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.StarredSyncViewModel;
import java.util.stream.Collectors;
public class StarredSyncDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogStarredSyncBinding bind;
private StarredSyncViewModel starredSyncViewModel;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
starredSyncViewModel = new ViewModelProvider(requireActivity()).get(StarredSyncViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setTitle(R.string.starred_sync_dialog_title)
.setPositiveButton(R.string.starred_sync_dialog_positive_button, null)
.setNeutralButton(R.string.starred_sync_dialog_neutral_button, null)
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null);
return builder.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction(requireContext());
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction(Context context) {
AlertDialog dialog = ((AlertDialog) getDialog());
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
starredSyncViewModel.getStarredTracks(requireActivity()).observe(requireActivity(), songs -> {
if (songs != null) {
DownloadUtil.getDownloadTracker(context).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
}
dialog.dismiss();
});
});
Button neutralButton = dialog.getButton(Dialog.BUTTON_NEUTRAL);
neutralButton.setOnClickListener(v -> {
Preferences.setStarredSyncEnabled(true);
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
Preferences.setStarredSyncEnabled(false);
dialog.dismiss();
});
}
}
}

View file

@ -0,0 +1,186 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.AlbumCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "ArtistCatalogueFragment";
private FragmentAlbumCatalogueBinding bind;
private MainActivity activity;
private AlbumCatalogueViewModel albumCatalogueViewModel;
private AlbumCatalogueAdapter albumAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
initData();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initAlbumCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initData() {
albumCatalogueViewModel = new ViewModelProvider(requireActivity()).get(AlbumCatalogueViewModel.class);
albumCatalogueViewModel.loadAlbums(500);
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.album_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initAlbumCatalogueView() {
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumCatalogueAdapter(this);
albumAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.albumCatalogueRecyclerView.setAdapter(albumAdapter);
albumCatalogueViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> albumAdapter.setItems(albums));
bind.albumCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_album_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
albumAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_album_sort_name) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_artist) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_ARTIST);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_year) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_YEAR);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_random) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,122 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel;
@OptIn(markerClass = UnstableApi.class)
public class AlbumListPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumListPageBinding bind;
private MainActivity activity;
private AlbumListPageViewModel albumListPageViewModel;
private AlbumHorizontalAdapter albumHorizontalAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
albumListPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumListPageViewModel.class);
init();
initAppBar();
initAlbumListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.ALBUM_RECENTLY_PLAYED) != null) {
albumListPageViewModel.title = Constants.ALBUM_RECENTLY_PLAYED;
bind.pageTitleLabel.setText(R.string.album_list_page_recently_played);
} else if (requireArguments().getString(Constants.ALBUM_MOST_PLAYED) != null) {
albumListPageViewModel.title = Constants.ALBUM_MOST_PLAYED;
bind.pageTitleLabel.setText(R.string.album_list_page_most_played);
} else if (requireArguments().getString(Constants.ALBUM_RECENTLY_ADDED) != null) {
albumListPageViewModel.title = Constants.ALBUM_RECENTLY_ADDED;
bind.pageTitleLabel.setText(R.string.album_list_page_recently_added);
} else if (requireArguments().getString(Constants.ALBUM_STARRED) != null) {
albumListPageViewModel.title = Constants.ALBUM_STARRED;
bind.pageTitleLabel.setText(R.string.album_list_page_starred);
} else if (requireArguments().getString(Constants.ALBUM_NEW_RELEASES) != null) {
albumListPageViewModel.title = Constants.ALBUM_NEW_RELEASES;
bind.pageTitleLabel.setText(R.string.album_list_page_new_releases);
} else if (requireArguments().getString(Constants.ALBUM_DOWNLOADED) != null) {
albumListPageViewModel.title = Constants.ALBUM_DOWNLOADED;
bind.pageTitleLabel.setText(R.string.album_list_page_downloaded);
} else if (requireArguments().getParcelable(Constants.ARTIST_OBJECT) != null) {
albumListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
albumListPageViewModel.title = Constants.ALBUM_FROM_ARTIST;
bind.pageTitleLabel.setText(MusicUtil.getReadableString(albumListPageViewModel.artist.getName()));
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.album_list_page_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initAlbumListView() {
bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.albumListRecyclerView.setHasFixedSize(true);
albumHorizontalAdapter = new AlbumHorizontalAdapter(
this,
(albumListPageViewModel.title.equals(Constants.ALBUM_DOWNLOADED) || albumListPageViewModel.title.equals(Constants.ALBUM_FROM_ARTIST))
);
bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter);
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> albumHorizontalAdapter.setItems(albums));
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,208 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentAlbumPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class AlbumPageFragment extends Fragment implements ClickCallback {
private FragmentAlbumPageBinding bind;
private MainActivity activity;
private AlbumPageViewModel albumPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.album_page_menu, menu);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentAlbumPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
albumPageViewModel = new ViewModelProvider(requireActivity()).get(AlbumPageViewModel.class);
init();
initAppBar();
initAlbumInfoTextButton();
initMusicButton();
initBackCover();
initSongsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_album) {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
});
return true;
}
return false;
}
private void init() {
albumPageViewModel.setAlbum(requireArguments().getParcelable(Constants.ALBUM_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.animToolbar.setTitle(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumNameLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumArtistLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getArtist()));
bind.albumReleaseYearLabel.setText(albumPageViewModel.getAlbum().getYear() != 0 ? String.valueOf(albumPageViewModel.getAlbum().getYear()) : "");
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initAlbumInfoTextButton() {
bind.albumArtistLabel.setOnClickListener(v -> albumPageViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
activity.navController.navigate(R.id.action_albumPageFragment_to_artistPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.album_error_retrieving_artist), Toast.LENGTH_SHORT).show();
}));
}
private void initMusicButton() {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null && !songs.isEmpty()) {
bind.albumPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
bind.albumPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
}
if (bind != null && songs.isEmpty()) {
bind.albumPagePlayButton.setEnabled(false);
bind.albumPageShuffleButton.setEnabled(false);
}
});
}
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId())
.build()
.into(bind.albumCoverImageView);
}
private void initSongsView() {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,179 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistCatalogueViewModel;
@UnstableApi
public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "ArtistCatalogueFragment";
private FragmentArtistCatalogueBinding bind;
private MainActivity activity;
private ArtistCatalogueViewModel artistCatalogueViewModel;
private ArtistCatalogueAdapter artistAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
initData();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initArtistCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initData() {
artistCatalogueViewModel = new ViewModelProvider(requireActivity()).get(ArtistCatalogueViewModel.class);
artistCatalogueViewModel.loadArtists();
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.artist_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initArtistCatalogueView() {
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.artistCatalogueRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistCatalogueAdapter(this);
artistAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.artistCatalogueRecyclerView.setAdapter(artistAdapter);
artistCatalogueViewModel.getArtistList().observe(getViewLifecycleOwner(), artistList -> artistAdapter.setItems(artistList));
bind.artistCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.artistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_artist_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
artistAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_artist_sort_name) {
artistAdapter.sort(Constants.ARTIST_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_artist_sort_random) {
artistAdapter.sort(Constants.ARTIST_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,101 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel;
@UnstableApi
public class ArtistListPageFragment extends Fragment implements ClickCallback {
private FragmentArtistListPageBinding bind;
private MainActivity activity;
private ArtistListPageViewModel artistListPageViewModel;
private ArtistHorizontalAdapter artistHorizontalAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
artistListPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistListPageViewModel.class);
init();
initAppBar();
initArtistListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.ARTIST_STARRED) != null) {
artistListPageViewModel.title = Constants.ARTIST_STARRED;
bind.pageTitleLabel.setText(R.string.artist_list_page_starred);
} else if (requireArguments().getString(Constants.ARTIST_DOWNLOADED) != null) {
artistListPageViewModel.title = Constants.ARTIST_DOWNLOADED;
bind.pageTitleLabel.setText(R.string.artist_list_page_downloaded);
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.artist_list_page_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initArtistListView() {
bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.artistListRecyclerView.setHasFixedSize(true);
artistHorizontalAdapter = new ArtistHorizontalAdapter(this);
bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter);
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> artistHorizontalAdapter.setItems(artists));
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumListPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,260 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class ArtistPageFragment extends Fragment implements ClickCallback {
private FragmentArtistPageBinding bind;
private MainActivity activity;
private ArtistPageViewModel artistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
private ArtistSimilarAdapter artistSimilarAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentArtistPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
init();
initAppBar();
initArtistInfo();
initPlayButtons();
initTopSongsView();
initAlbumsView();
initSimilarArtistsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
artistPageViewModel.setArtist(requireArguments().getParcelable(Constants.ARTIST_OBJECT));
bind.mostStreamedSongTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_ARTIST, Constants.MEDIA_BY_ARTIST);
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(artistPageViewModel.getArtist().getName()));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
}
private void initArtistInfo() {
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artistInfo -> {
if(artistInfo == null) {
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
} else {
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
if (bind != null)
bind.artistPageBioSector.setVisibility(!normalizedBio.trim().isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), artistPageViewModel.getArtist().getId())
.build()
.into(bind.artistBackdropImageView);
if (bind != null) bind.bioTextView.setText(normalizedBio);
if (bind != null) bind.bioMoreTextViewClickable.setOnClickListener(v -> {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(artistInfo.getLastFmUrl()));
startActivity(intent);
});
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
}
});
}
private void initPlayButtons() {
bind.artistPageShuffleButton.setOnClickListener(v -> {
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
}
});
});
bind.artistPageRadioButton.setOnClickListener(v -> {
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_radio), Toast.LENGTH_SHORT).show();
}
});
});
}
private void initTopSongsView() {
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
songHorizontalAdapter.setItems(songs);
}
});
}
private void initAlbumsView() {
bind.albumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
bind.albumsRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumArtistPageOrSimilarAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumsRecyclerView);
}
private void initSimilarArtistsView() {
bind.similarArtistsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarArtistsRecyclerView.setHasFixedSize(true);
artistSimilarAdapter = new ArtistSimilarAdapter(this);
bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
if (artist == null) {
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.similarArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistSimilarAdapter.setItems(artist.getSimilarArtists());
}
});
CustomLinearSnapHelper similarArtistSnapHelper = new CustomLinearSnapHelper();
similarArtistSnapHelper.attachToRecyclerView(bind.similarArtistsRecyclerView);
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,172 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Artist;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DirectoryViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
@UnstableApi
public class DirectoryFragment extends Fragment implements ClickCallback {
private static final String TAG = "DirectoryFragment";
private FragmentDirectoryBinding bind;
private MainActivity activity;
private DirectoryViewModel directoryViewModel;
private MusicDirectoryAdapter musicDirectoryAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDirectoryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
initAppBar();
initButtons();
initDirectoryListView();
init();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
Artist artist = getArguments().getParcelable(Constants.MUSIC_INDEX_OBJECT);
if (artist != null) {
directoryViewModel.setMusicDirectoryId(artist.getId());
directoryViewModel.setMusicDirectoryName(artist.getName());
}
directoryViewModel.loadMusicDirectory(getViewLifecycleOwner());
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.directoryInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
bind.toolbar.setTitle(directory.getName());
}
});
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
bind.directoryTitleLabel.setText(directory.getName());
}
});
}
private void initButtons() {
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null && directory.getParentId() != null && !Objects.equals(directory.getParentId(), "-1")) {
bind.directoryBackImageView.setVisibility(View.VISIBLE);
} else {
bind.directoryBackImageView.setVisibility(View.GONE);
}
});
bind.directoryBackImageView.setOnClickListener(v -> directoryViewModel.goBack());
}
private void initDirectoryListView() {
bind.directoryRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.directoryRecyclerView.setHasFixedSize(true);
musicDirectoryAdapter = new MusicDirectoryAdapter(this);
bind.directoryRecyclerView.setAdapter(musicDirectoryAdapter);
directoryViewModel.getDirectory().observe(getViewLifecycleOwner(), directory -> {
if (directory != null) {
musicDirectoryAdapter.setItems(directory.getChildren());
} else {
musicDirectoryAdapter.setItems(Collections.emptyList());
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
}
@Override
public void onMusicDirectoryClick(Bundle bundle) {
Child child = bundle.getParcelable(Constants.MUSIC_DIRECTORY_OBJECT);
if (child != null) {
directoryViewModel.setMusicDirectoryId(child.getId());
directoryViewModel.setMusicDirectoryName(child.getTitle());
}
}
}

View file

@ -0,0 +1,170 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDownloadBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.DownloadHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
@UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback {
private FragmentDownloadBinding bind;
private MainActivity activity;
private DownloadViewModel downloadViewModel;
private DownloadHorizontalAdapter downloadHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentDownloadBinding.inflate(inflater, container, false);
View view = bind.getRoot();
downloadViewModel = new ViewModelProvider(requireActivity()).get(DownloadViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initDownloadedSongView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_downloadFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_downloadFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initDownloadedSongView() {
bind.downloadedTracksRecyclerView.setHasFixedSize(true);
downloadHorizontalAdapter = new DownloadHorizontalAdapter(this);
bind.downloadedTracksRecyclerView.setAdapter(downloadHorizontalAdapter);
downloadViewModel.getDownloadedTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs != null) {
if (songs.isEmpty()) {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksSector.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedTracksPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedTracksSector.setVisibility(View.VISIBLE);
bind.downloadedTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
downloadHorizontalAdapter.setItems(songs);
}
}
if (bind != null) bind.loadingProgressBar.setVisibility(View.GONE);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,106 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentFilterBinding;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.FilterViewModel;
import com.google.android.material.chip.Chip;
@OptIn(markerClass = UnstableApi.class)
public class FilterFragment extends Fragment {
private static final String TAG = "FilterFragment";
private MainActivity activity;
private FragmentFilterBinding bind;
private FilterViewModel filterViewModel;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentFilterBinding.inflate(inflater, container, false);
View view = bind.getRoot();
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
init();
initAppBar();
setFilterChips();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_BY_GENRES, Constants.MEDIA_BY_GENRES);
bundle.putStringArrayList("filters_list", filterViewModel.getFilters());
bundle.putStringArrayList("filter_name_list", filterViewModel.getFilterNames());
bind.finishFilteringTextViewClickable.setOnClickListener(v -> {
if (filterViewModel.getFilters().size() > 1)
activity.navController.navigate(R.id.action_filterFragment_to_songListPageFragment, bundle);
else
Toast.makeText(requireContext(), getString(R.string.filter_info_selection), Toast.LENGTH_SHORT).show();
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.genreFilterInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.filter_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void setFilterChips() {
filterViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> {
bind.loadingProgressBar.setVisibility(View.GONE);
bind.filterContainer.setVisibility(View.VISIBLE);
for (Genre genre : genres) {
Chip chip = (Chip) requireActivity().getLayoutInflater().inflate(R.layout.chip_search_filter_genre, null, false);
chip.setText(MusicUtil.getReadableString(genre.getGenre()));
chip.setChecked(filterViewModel.getFilters().contains(genre.getGenre()));
chip.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (isChecked)
filterViewModel.addFilter(genre.getGenre(), buttonView.getText().toString());
else
filterViewModel.removeFilter(genre.getGenre(), buttonView.getText().toString());
});
bind.filtersChipsGroup.addView(chip);
}
});
}
}

View file

@ -0,0 +1,172 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentGenreCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.GenreCatalogueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.GenreCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class GenreCatalogueFragment extends Fragment implements ClickCallback {
private FragmentGenreCatalogueBinding bind;
private MainActivity activity;
private GenreCatalogueViewModel genreCatalogueViewModel;
private GenreCatalogueAdapter genreCatalogueAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentGenreCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
genreCatalogueViewModel = new ViewModelProvider(requireActivity()).get(GenreCatalogueViewModel.class);
init();
initAppBar();
initGenreCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.filterGenresTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_genreCatalogueFragment_to_filterFragment));
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.genreInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.genre_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initGenreCatalogueView() {
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 16, false));
bind.genreCatalogueRecyclerView.setHasFixedSize(true);
genreCatalogueAdapter = new GenreCatalogueAdapter(this);
genreCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.genreCatalogueRecyclerView.setAdapter(genreCatalogueAdapter);
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres));
bind.genreCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.genreListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_genre_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
genreCatalogueAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_genre_sort_name) {
genreCatalogueAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_genre_sort_random) {
genreCatalogueAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
hideKeyboard(requireView());
}
}

View file

@ -0,0 +1,117 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.fragment.pager.HomePager;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.material.tabs.TabLayoutMediator;
import java.util.Objects;
@UnstableApi
public class HomeFragment extends Fragment {
private static final String TAG = "HomeFragment";
private FragmentHomeBinding bind;
private MainActivity activity;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeBinding.inflate(inflater, container, false);
return bind.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initHomePager();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
activity.setBottomSheetVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_homeFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_homeFragment_to_settingsFragment);
return true;
}
return false;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initHomePager() {
HomePager pager = new HomePager(this);
pager.addFragment(new HomeTabMusicFragment(), "Music", R.drawable.ic_home);
if (Preferences.isPodcastSectionVisible())
pager.addFragment(new HomeTabPodcastFragment(), "Podcast", R.drawable.ic_graphic_eq);
if (Preferences.isRadioSectionVisible())
pager.addFragment(new HomeTabRadioFragment(), "Radio", R.drawable.ic_play_for_work);
bind.homeViewPager.setAdapter(pager);
bind.homeViewPager.setUserInputEnabled(false);
new TabLayoutMediator(bind.homeTabLayout, bind.homeViewPager,
(tab, position) -> {
tab.setText(pager.getPageTitle(position));
// tab.setIcon(pager.getPageIcon(position));
}
).attach();
bind.homeTabLayout.setVisibility(Preferences.isPodcastSectionVisible() || Preferences.isRadioSectionVisible() ? View.VISIBLE : View.GONE);
}
}

View file

@ -0,0 +1,715 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.PagerSnapHelper;
import androidx.recyclerview.widget.SnapHelper;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabMusicBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.helper.recyclerview.DotsIndicatorDecoration;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter;
import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.List;
@UnstableApi
public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private static final String TAG = "HomeFragment";
private FragmentHomeTabMusicBinding bind;
private MainActivity activity;
private HomeViewModel homeViewModel;
private DiscoverSongAdapter discoverSongAdapter;
private SimilarTrackAdapter similarMusicAdapter;
private ArtistAdapter radioArtistAdapter;
private ArtistAdapter bestOfArtistAdapter;
private SongHorizontalAdapter starredSongAdapter;
private AlbumHorizontalAdapter starredAlbumAdapter;
private ArtistHorizontalAdapter starredArtistAdapter;
private AlbumAdapter recentlyAddedAlbumAdapter;
private AlbumAdapter recentlyPlayedAlbumAdapter;
private AlbumAdapter mostPlayedAlbumAdapter;
private AlbumHorizontalAdapter newReleasesAlbumAdapter;
private YearAdapter yearAdapter;
private GridTrackAdapter gridTrackAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabMusicBinding.inflate(inflater, container, false);
View view = bind.getRoot();
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initSyncStarredView();
initDiscoverSongSlideView();
initSimilarSongView();
initArtistRadio();
initArtistBestOf();
initStarredTracksView();
initStarredAlbumsView();
initStarredArtistsView();
initMostPlayedAlbumView();
initRecentPlayedAlbumView();
initNewReleasesView();
initYearSongView();
initRecentAddedAlbumView();
initGridView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.discoveryTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshDiscoverySongSample(getViewLifecycleOwner());
return true;
});
bind.similarTracksTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshSimilarSongSample(getViewLifecycleOwner());
return true;
});
bind.radioArtistTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshRadioArtistSample(getViewLifecycleOwner());
return true;
});
bind.bestOfArtistTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshBestOfArtist(getViewLifecycleOwner());
return true;
});
bind.starredTracksTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.MEDIA_STARRED, Constants.MEDIA_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_songListPageFragment, bundle);
});
bind.starredAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_STARRED, Constants.ALBUM_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.starredArtistsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ARTIST_STARRED, Constants.ARTIST_STARRED);
activity.navController.navigate(R.id.action_homeFragment_to_artistListPageFragment, bundle);
});
bind.recentlyAddedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_RECENTLY_ADDED, Constants.ALBUM_RECENTLY_ADDED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.recentlyPlayedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_RECENTLY_PLAYED, Constants.ALBUM_RECENTLY_PLAYED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.mostPlayedAlbumsTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.ALBUM_MOST_PLAYED, Constants.ALBUM_MOST_PLAYED);
activity.navController.navigate(R.id.action_homeFragment_to_albumListPageFragment, bundle);
});
bind.starredTracksTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredTracks(getViewLifecycleOwner());
return true;
});
bind.starredAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredAlbums(getViewLifecycleOwner());
return true;
});
bind.starredArtistsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshStarredArtists(getViewLifecycleOwner());
return true;
});
bind.recentlyPlayedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshRecentlyPlayedAlbumList(getViewLifecycleOwner());
return true;
});
bind.mostPlayedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshMostPlayedAlbums(getViewLifecycleOwner());
return true;
});
bind.recentlyAddedAlbumsTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner());
return true;
});
}
private void initSyncStarredView() {
if (Preferences.isStarredSyncEnabled()) {
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> songs) {
if (songs != null) {
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
List<String> toSync = new ArrayList<>();
for (Child song : songs) {
if (!manager.isDownloaded(song.getId())) {
toSync.add(song.getTitle());
}
}
if (!toSync.isEmpty()) {
bind.homeSyncStarredCard.setVisibility(View.VISIBLE);
bind.homeSyncStarredTracksToSync.setText(String.join(", ", toSync));
}
}
homeViewModel.getAllStarredTracks().removeObserver(this);
}
});
}
bind.homeSyncStarredCancel.setOnClickListener(v -> bind.homeSyncStarredCard.setVisibility(View.GONE));
bind.homeSyncStarredDownload.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
homeViewModel.getAllStarredTracks().observeForever(new Observer<List<Child>>() {
@Override
public void onChanged(List<Child> songs) {
if (songs != null) {
DownloaderManager manager = DownloadUtil.getDownloadTracker(requireContext());
for (Child song : songs) {
if (!manager.isDownloaded(song.getId())) {
manager.download(MappingUtil.mapDownload(song), new Download(song));
}
}
}
homeViewModel.getAllStarredTracks().removeObserver(this);
bind.homeSyncStarredCard.setVisibility(View.GONE);
}
});
}
});
}
private void initDiscoverSongSlideView() {
bind.discoverSongViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
discoverSongAdapter = new DiscoverSongAdapter(this);
bind.discoverSongViewPager.setAdapter(discoverSongAdapter);
bind.discoverSongViewPager.setOffscreenPageLimit(1);
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
discoverSongAdapter.setItems(songs);
}
});
setSlideViewOffset(bind.discoverSongViewPager, 20, 16);
}
private void initSimilarSongView() {
bind.similarTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarTracksRecyclerView.setHasFixedSize(true);
similarMusicAdapter = new SimilarTrackAdapter(this);
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
similarMusicAdapter.setItems(songs);
}
});
CustomLinearSnapHelper similarSongSnapHelper = new CustomLinearSnapHelper();
similarSongSnapHelper.attachToRecyclerView(bind.similarTracksRecyclerView);
}
private void initArtistBestOf() {
bind.bestOfArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.bestOfArtistRecyclerView.setHasFixedSize(true);
bestOfArtistAdapter = new ArtistAdapter(this, false, true);
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
bestOfArtistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistBestOfSnapHelper = new CustomLinearSnapHelper();
artistBestOfSnapHelper.attachToRecyclerView(bind.bestOfArtistRecyclerView);
}
private void initArtistRadio() {
bind.radioArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.radioArtistRecyclerView.setHasFixedSize(true);
radioArtistAdapter = new ArtistAdapter(this, true, false);
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
radioArtistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistRadioSnapHelper = new CustomLinearSnapHelper();
artistRadioSnapHelper.attachToRecyclerView(bind.radioArtistRecyclerView);
}
private void initGridView() {
bind.gridTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
bind.gridTracksRecyclerView.addItemDecoration(new GridItemDecoration(3, 8, false));
bind.gridTracksRecyclerView.setHasFixedSize(true);
gridTrackAdapter = new GridTrackAdapter(this);
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.size() == 0) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
gridTrackAdapter.setItems(chronologies);
}
});
}
private void initStarredTracksView() {
bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, true);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredSongAdapter.setItems(songs);
}
});
SnapHelper starredTrackSnapHelper = new PagerSnapHelper();
starredTrackSnapHelper.attachToRecyclerView(bind.starredTracksRecyclerView);
bind.starredTracksRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initStarredAlbumsView() {
bind.starredAlbumsRecyclerView.setHasFixedSize(true);
starredAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredAlbumAdapter.setItems(albums);
}
});
SnapHelper starredAlbumSnapHelper = new PagerSnapHelper();
starredAlbumSnapHelper.attachToRecyclerView(bind.starredAlbumsRecyclerView);
bind.starredAlbumsRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initStarredArtistsView() {
bind.starredArtistsRecyclerView.setHasFixedSize(true);
starredArtistAdapter = new ArtistHorizontalAdapter(this);
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredArtistAdapter.setItems(artists);
}
});
SnapHelper starredArtistSnapHelper = new PagerSnapHelper();
starredArtistSnapHelper.attachToRecyclerView(bind.starredArtistsRecyclerView);
bind.starredArtistsRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initNewReleasesView() {
bind.newReleasesRecyclerView.setHasFixedSize(true);
newReleasesAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
newReleasesAlbumAdapter.setItems(albums);
}
});
SnapHelper newReleasesSnapHelper = new PagerSnapHelper();
newReleasesSnapHelper.attachToRecyclerView(bind.newReleasesRecyclerView);
bind.newReleasesRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void initYearSongView() {
bind.yearsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.yearsRecyclerView.setHasFixedSize(true);
yearAdapter = new YearAdapter(this);
bind.yearsRecyclerView.setAdapter(yearAdapter);
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
if (years == null) {
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
yearAdapter.setItems(years);
}
});
CustomLinearSnapHelper yearSnapHelper = new CustomLinearSnapHelper();
yearSnapHelper.attachToRecyclerView(bind.yearsRecyclerView);
}
private void initMostPlayedAlbumView() {
bind.mostPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.mostPlayedAlbumsRecyclerView.setHasFixedSize(true);
mostPlayedAlbumAdapter = new AlbumAdapter(this);
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
// if (albums.size() < 5) reorder();
mostPlayedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper mostPlayedAlbumSnapHelper = new CustomLinearSnapHelper();
mostPlayedAlbumSnapHelper.attachToRecyclerView(bind.mostPlayedAlbumsRecyclerView);
}
private void initRecentPlayedAlbumView() {
bind.recentlyPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyPlayedAlbumsRecyclerView.setHasFixedSize(true);
recentlyPlayedAlbumAdapter = new AlbumAdapter(this);
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyPlayedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper recentPlayedAlbumSnapHelper = new CustomLinearSnapHelper();
recentPlayedAlbumSnapHelper.attachToRecyclerView(bind.recentlyPlayedAlbumsRecyclerView);
}
private void initRecentAddedAlbumView() {
bind.recentlyAddedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyAddedAlbumsRecyclerView.setHasFixedSize(true);
recentlyAddedAlbumAdapter = new AlbumAdapter(this);
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyAddedAlbumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper recentAddedAlbumSnapHelper = new CustomLinearSnapHelper();
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
}
private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) {
viewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
if (viewPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(viewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.setTranslationX(-myOffset);
} else {
page.setTranslationX(myOffset);
}
} else {
page.setTranslationY(myOffset);
}
});
}
public void reorder() {
if (bind != null) {
// bind.homeLinearLayoutContainer.removeAllViews();
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
if (bundle.containsKey(Constants.MEDIA_MIX)) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelable(Constants.TRACK_OBJECT));
activity.setBottomSheetInPeek(true);
if (mediaBrowserListenableFuture != null) {
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs != null && songs.size() > 0) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
}
});
}
} else if (bundle.containsKey(Constants.MEDIA_CHRONOLOGY)) {
List<Child> media = bundle.getParcelableArrayList(Constants.TRACKS_OBJECT);
MediaManager.startQueue(mediaBrowserListenableFuture, media, bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
} else {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
if (bundle.containsKey(Constants.MEDIA_MIX) && bundle.getBoolean(Constants.MEDIA_MIX)) {
Snackbar.make(requireView(), R.string.artist_adapter_radio_station_starting, Snackbar.LENGTH_LONG)
.setAnchorView(activity.bind.playerBottomSheet)
.show();
if (mediaBrowserListenableFuture != null) {
homeViewModel.getArtistInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
});
}
} else if (bundle.containsKey(Constants.MEDIA_BEST_OF) && bundle.getBoolean(Constants.MEDIA_BEST_OF)) {
if (mediaBrowserListenableFuture != null) {
homeViewModel.getArtistBestOf(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
});
}
} else {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onYearClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
}

View file

@ -0,0 +1,188 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabPodcastBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastChannelHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.PodcastEpisodeAdapter;
import com.cappielloantonio.tempo.ui.dialog.PodcastChannelEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class HomeTabPodcastFragment extends Fragment implements ClickCallback, PodcastCallback {
private static final String TAG = "HomeTabPodcastFragment";
private FragmentHomeTabPodcastBinding bind;
private MainActivity activity;
private PodcastViewModel podcastViewModel;
private PodcastEpisodeAdapter podcastEpisodeAdapter;
private PodcastChannelHorizontalAdapter podcastChannelHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabPodcastBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastViewModel = new ViewModelProvider(requireActivity()).get(PodcastViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
initPodcastView();
initNewestPodcastsView();
initPodcastChannelsView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.podcastChannelsPreTextView.setOnClickListener(v -> {
PodcastChannelEditorDialog dialog = new PodcastChannelEditorDialog(this);
dialog.show(activity.getSupportFragmentManager(), null);
});
bind.podcastChannelsTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_homeFragment_to_podcastChannelCatalogueFragment));
bind.hideSectionButton.setOnClickListener(v -> Preferences.setPodcastSectionHidden());
}
private void initPodcastView() {
podcastViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastChannels -> {
if (podcastChannels == null) {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(View.GONE);
if (bind != null) bind.emptyPodcastLayout.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.emptyPodcastLayout.setVisibility(podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
}
});
}
private void initPodcastChannelsView() {
bind.podcastChannelsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
podcastChannelHorizontalAdapter = new PodcastChannelHorizontalAdapter(this);
bind.podcastChannelsRecyclerView.setAdapter(podcastChannelHorizontalAdapter);
podcastViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastChannels -> {
if (podcastChannels == null) {
if (bind != null) bind.homePodcastChannelsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homePodcastChannelsSector.setVisibility(!podcastChannels.isEmpty() ? View.VISIBLE : View.GONE);
podcastChannelHorizontalAdapter.setItems(podcastChannels);
}
});
}
private void initNewestPodcastsView() {
bind.newestPodcastsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.newestPodcastsRecyclerView.addItemDecoration(UIUtil.getDividerItemDecoration(requireContext()));
podcastEpisodeAdapter = new PodcastEpisodeAdapter(this);
bind.newestPodcastsRecyclerView.setAdapter(podcastEpisodeAdapter);
podcastViewModel.getNewestPodcastEpisodes(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), podcastEpisodes -> {
if (podcastEpisodes == null) {
if (bind != null) bind.homeNewestPodcastsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeNewestPodcastsSector.setVisibility(!podcastEpisodes.isEmpty() ? View.VISIBLE : View.GONE);
podcastEpisodeAdapter.setItems(podcastEpisodes.stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList()));
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onPodcastEpisodeClick(Bundle bundle) {
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
}
@Override
public void onPodcastChannelClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelPageFragment, bundle);
}
@Override
public void onPodcastChannelLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelBottomSheetDialog, bundle);
}
@Override
public void onDismiss() {
new Handler().postDelayed(() -> {
if (podcastViewModel != null) podcastViewModel.refreshPodcastChannels(getViewLifecycleOwner());
if (podcastViewModel != null) podcastViewModel.refreshNewestPodcastEpisodes(getViewLifecycleOwner());
}, 1000);
}
}

View file

@ -0,0 +1,146 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.databinding.FragmentHomeTabRadioBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.RadioCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.InternetRadioStationAdapter;
import com.cappielloantonio.tempo.ui.dialog.RadioEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.RadioViewModel;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class HomeTabRadioFragment extends Fragment implements ClickCallback, RadioCallback {
private static final String TAG = "HomeTabRadioFragment";
private FragmentHomeTabRadioBinding bind;
private MainActivity activity;
private RadioViewModel radioViewModel;
private InternetRadioStationAdapter internetRadioStationAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentHomeTabRadioBinding.inflate(inflater, container, false);
View view = bind.getRoot();
radioViewModel = new ViewModelProvider(requireActivity()).get(RadioViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
init();
initRadioStationView();
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
bind.internetRadioStationPreTextView.setOnClickListener(v -> {
RadioEditorDialog dialog = new RadioEditorDialog(this);
dialog.show(activity.getSupportFragmentManager(), null);
});
bind.internetRadioStationTitleTextView.setOnLongClickListener(v -> {
radioViewModel.getInternetRadioStations(getViewLifecycleOwner());
return true;
});
bind.hideSectionButton.setOnClickListener(v -> Preferences.setRadioSectionHidden());
}
private void initRadioStationView() {
bind.internetRadioStationRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.internetRadioStationRecyclerView.setHasFixedSize(true);
internetRadioStationAdapter = new InternetRadioStationAdapter(this);
bind.internetRadioStationRecyclerView.setAdapter(internetRadioStationAdapter);
radioViewModel.getInternetRadioStations(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), internetRadioStations -> {
if (internetRadioStations == null) {
if (bind != null) bind.homeRadioStationSector.setVisibility(View.GONE);
if (bind != null) bind.emptyRadioStationLayout.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeRadioStationSector.setVisibility(!internetRadioStations.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.emptyRadioStationLayout.setVisibility(internetRadioStations.isEmpty() ? View.VISIBLE : View.GONE);
internetRadioStationAdapter.setItems(internetRadioStations);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onInternetRadioStationClick(Bundle bundle) {
MediaManager.startRadio(mediaBrowserListenableFuture, bundle.getParcelable(Constants.INTERNET_RADIO_STATION_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onInternetRadioStationLongClick(Bundle bundle) {
RadioEditorDialog dialog = new RadioEditorDialog(() -> radioViewModel.getInternetRadioStations(getViewLifecycleOwner()));
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onDismiss() {
new Handler().postDelayed(() -> {
if (radioViewModel != null)
radioViewModel.refreshInternetRadioStations(getViewLifecycleOwner());
}, 1000);
}
}

View file

@ -0,0 +1,105 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentIndexBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicIndexAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.IndexUtil;
import com.cappielloantonio.tempo.viewmodel.IndexViewModel;
@UnstableApi
public class IndexFragment extends Fragment implements ClickCallback {
private static final String TAG = "IndexFragment";
private FragmentIndexBinding bind;
private MainActivity activity;
private IndexViewModel indexViewModel;
private MusicIndexAdapter musicIndexAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentIndexBinding.inflate(inflater, container, false);
View view = bind.getRoot();
indexViewModel = new ViewModelProvider(requireActivity()).get(IndexViewModel.class);
initAppBar();
initDirectoryListView();
init();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
if (musicFolder != null) {
indexViewModel.setMusicFolder(musicFolder);
bind.indexTitleLabel.setText(musicFolder.getName());
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.indexInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(indexViewModel.getMusicFolderName());
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initDirectoryListView() {
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.indexRecyclerView.setHasFixedSize(true);
musicIndexAdapter = new MusicIndexAdapter(this);
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> {
if (indexes != null) {
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
}
});
}
@Override
public void onMusicIndexClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
}
}

View file

@ -0,0 +1,17 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
import com.cappielloantonio.tempo.R;
public class LandingFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_landing, container, false);
}
}

View file

@ -0,0 +1,308 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.GenreAdapter;
import com.cappielloantonio.tempo.ui.adapter.MusicFolderAdapter;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
import com.google.android.gms.cast.framework.CastButtonFactory;
import java.util.Objects;
@UnstableApi
public class LibraryFragment extends Fragment implements ClickCallback {
private static final String TAG = "LibraryFragment";
private FragmentLibraryBinding bind;
private MainActivity activity;
private LibraryViewModel libraryViewModel;
private MusicFolderAdapter musicFolderAdapter;
private AlbumAdapter albumAdapter;
private ArtistAdapter artistAdapter;
private GenreAdapter genreAdapter;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.main_page_menu, menu);
CastButtonFactory.setUpMediaRouteButton(requireContext(), menu, R.id.media_route_menu_item);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentLibraryBinding.inflate(inflater, container, false);
View view = bind.getRoot();
libraryViewModel = new ViewModelProvider(requireActivity()).get(LibraryViewModel.class);
init();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initAppBar();
initMusicFolderView();
initAlbumView();
initArtistView();
initGenreView();
initPlaylistSlideView();
}
@Override
public void onStart() {
super.onStart();
activity.setBottomNavigationBarVisibility(true);
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_search) {
activity.navController.navigate(R.id.action_libraryFragment_to_searchFragment);
return true;
} else if (item.getItemId() == R.id.action_settings) {
activity.navController.navigate(R.id.action_libraryFragment_to_settingsFragment);
return true;
}
return false;
}
private void init() {
bind.albumCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_albumCatalogueFragment));
bind.artistCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_artistCatalogueFragment));
bind.genreCatalogueTextViewClickable.setOnClickListener(v -> activity.navController.navigate(R.id.action_libraryFragment_to_genreCatalogueFragment));
bind.playlistCatalogueTextViewClickable.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putString(Constants.PLAYLIST_ALL, Constants.PLAYLIST_ALL);
activity.navController.navigate(R.id.action_libraryFragment_to_playlistCatalogueFragment, bundle);
});
bind.albumCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshAlbumSample(getViewLifecycleOwner());
return true;
});
bind.artistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshArtistSample(getViewLifecycleOwner());
return true;
});
bind.genreCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshGenreSample(getViewLifecycleOwner());
return true;
});
bind.playlistCatalogueSampleTextViewRefreshable.setOnLongClickListener(view -> {
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
return true;
});
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
Objects.requireNonNull(bind.toolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicFolderView() {
bind.musicFolderRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.musicFolderRecyclerView.setHasFixedSize(true);
musicFolderAdapter = new MusicFolderAdapter(this);
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
musicFolderAdapter.setItems(musicFolders);
}
});
}
private void initAlbumView() {
bind.albumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.albumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumRecyclerView);
}
private void initArtistView() {
bind.artistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.artistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(artists);
}
});
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.artistRecyclerView);
}
private void initGenreView() {
bind.genreRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3, GridLayoutManager.HORIZONTAL, false));
bind.genreRecyclerView.setHasFixedSize(true);
genreAdapter = new GenreAdapter(this);
bind.genreRecyclerView.setAdapter(genreAdapter);
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
if (bind != null)
bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
genreAdapter.setItems(genres);
}
});
CustomLinearSnapHelper genreSnapHelper = new CustomLinearSnapHelper();
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
}
private void initPlaylistSlideView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
playlistHorizontalAdapter.setItems(playlists);
}
});
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
@Override
public void onGenreClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
@Override
public void onPlaylistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
@Override
public void onMusicFolderClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
}
}

View file

@ -0,0 +1,165 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.ui.adapter.ServerAdapter;
import com.cappielloantonio.tempo.databinding.FragmentLoginBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.SystemCallback;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.repository.SystemRepository;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.ServerSignupDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
@UnstableApi
public class LoginFragment extends Fragment implements ClickCallback {
private static final String TAG = "LoginFragment";
private FragmentLoginBinding bind;
private MainActivity activity;
private LoginViewModel loginViewModel;
private ServerAdapter serverAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.login_page_menu, menu);
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
bind = FragmentLoginBinding.inflate(inflater, container, false);
View view = bind.getRoot();
initAppBar();
initServerListView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.serverInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.login_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initServerListView() {
bind.serverListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.serverListRecyclerView.setHasFixedSize(true);
serverAdapter = new ServerAdapter(this);
bind.serverListRecyclerView.setAdapter(serverAdapter);
loginViewModel.getServerList().observe(getViewLifecycleOwner(), servers -> {
if (servers.size() > 0) {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.GONE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.VISIBLE);
serverAdapter.setItems(servers);
} else {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.VISIBLE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.GONE);
}
});
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_add) {
ServerSignupDialog dialog = new ServerSignupDialog();
dialog.show(activity.getSupportFragmentManager(), null);
return true;
}
return false;
}
@Override
public void onServerClick(Bundle bundle) {
Server server = bundle.getParcelable("server_object");
saveServerPreference(server.getServerId(), server.getAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
SystemRepository systemRepository = new SystemRepository();
systemRepository.checkUserCredential(new SystemCallback() {
@Override
public void onError(Exception exception) {
resetServerPreference();
Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_SHORT).show();
}
@Override
public void onSuccess(String password, String token, String salt) {
activity.goFromLogin();
}
});
}
@Override
public void onServerLongClick(Bundle bundle) {
ServerSignupDialog dialog = new ServerSignupDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}
private void saveServerPreference(String serverId, String server, String user, String password, boolean isLowSecurity) {
Preferences.setServerId(serverId);
Preferences.setServer(server);
Preferences.setUser(user);
Preferences.setPassword(password);
Preferences.setLowSecurity(isLowSecurity);
App.getSubsonicClientInstance(true);
}
private void resetServerPreference() {
Preferences.setServerId(null);
Preferences.setServer(null);
Preferences.setUser(null);
Preferences.setPassword(null);
Preferences.setToken(null);
Preferences.setSalt(null);
Preferences.setLowSecurity(false);
App.getSubsonicClientInstance(true);
}
}

View file

@ -0,0 +1,290 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.MediaController;
import androidx.media3.session.SessionToken;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlayerBottomSheetBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerVerticalPager;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.stream.IntStream;
@OptIn(markerClass = UnstableApi.class)
public class PlayerBottomSheetFragment extends Fragment {
private FragmentPlayerBottomSheetBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private Handler progressBarHandler;
private Runnable progressBarRunnable;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
bind = FragmentPlayerBottomSheetBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
customizeBottomSheetBackground();
initViewPager();
setHeaderBookmarksButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
bindMediaController();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void customizeBottomSheetBackground() {
bind.playerHeaderLayout.getRoot().setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 2));
}
private void initViewPager() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setOrientation(ViewPager2.ORIENTATION_VERTICAL);
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setAdapter(new PlayerControllerVerticalPager(this));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaController.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
defineProgressBarHandler(mediaBrowser);
setMediaControllerUI(mediaBrowser);
setMetadata(mediaBrowser.getMediaMetadata());
setContentDuration(mediaBrowser.getContentDuration());
setPlayingState(mediaBrowser.isPlaying());
setHeaderMediaController();
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaMetadata);
setContentDuration(mediaBrowser.getContentDuration());
}
@Override
public void onIsPlayingChanged(boolean isPlaying) {
setPlayingState(isPlaying);
}
@Override
public void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {
Player.Listener.super.onSkipSilenceEnabledChanged(skipSilenceEnabled);
}
@Override
public void onEvents(Player player, Player.Events events) {
setHeaderNextButtonState(mediaBrowser.hasNextMediaItem());
}
});
}
private void setMetadata(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("title")));
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"))
.build()
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage);
}
}
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
if (mediaBrowser.getMediaMetadata().extras != null) {
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
case Constants.MEDIA_TYPE_PODCAST:
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setVisibility(View.GONE);
break;
case Constants.MEDIA_TYPE_MUSIC:
default:
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setVisibility(View.VISIBLE);
break;
}
}
}
private void setContentDuration(long duration) {
bind.playerHeaderLayout.playerHeaderSeekBar.setMax((int) (duration / 1000));
}
private void setProgress(MediaBrowser mediaBrowser) {
if (bind != null)
bind.playerHeaderLayout.playerHeaderSeekBar.setProgress((int) (mediaBrowser.getCurrentPosition() / 1000), true);
}
private void setPlayingState(boolean isPlaying) {
bind.playerHeaderLayout.playerHeaderButton.setChecked(isPlaying);
runProgressBarHandler(isPlaying);
}
private void setHeaderMediaController() {
bind.playerHeaderLayout.playerHeaderButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_play_pause).performClick());
bind.playerHeaderLayout.playerHeaderNextMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_next).performClick());
bind.playerHeaderLayout.playerHeaderRewindMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_rew).performClick());
bind.playerHeaderLayout.playerHeaderFastForwardMediaButton.setOnClickListener(view -> bind.getRoot().findViewById(R.id.exo_ffwd).performClick());
}
private void setHeaderNextButtonState(boolean isEnabled) {
bind.playerHeaderLayout.playerHeaderNextMediaButton.setEnabled(isEnabled);
bind.playerHeaderLayout.playerHeaderNextMediaButton.setAlpha(isEnabled ? (float) 1.0 : (float) 0.3);
}
public View getPlayerHeader() {
return requireView().findViewById(R.id.player_header_layout);
}
public void goBackToFirstPage() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(0, false);
goToControllerPage();
}
public void goToControllerPage() {
PlayerControllerVerticalPager playerControllerVerticalPager = (PlayerControllerVerticalPager) bind.playerBodyLayout.playerBodyBottomSheetViewPager.getAdapter();
if (playerControllerVerticalPager != null) {
PlayerControllerFragment playerControllerFragment = (PlayerControllerFragment) playerControllerVerticalPager.getRegisteredFragment(0);
if (playerControllerFragment != null) {
playerControllerFragment.goToControllerPage();
}
}
}
public void goToLyricsPage() {
PlayerControllerVerticalPager playerControllerVerticalPager = (PlayerControllerVerticalPager) bind.playerBodyLayout.playerBodyBottomSheetViewPager.getAdapter();
if (playerControllerVerticalPager != null) {
PlayerControllerFragment playerControllerFragment = (PlayerControllerFragment) playerControllerVerticalPager.getRegisteredFragment(0);
if (playerControllerFragment != null) {
playerControllerFragment.goToLyricsPage();
}
}
}
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler();
progressBarRunnable = () -> {
setProgress(mediaBrowser);
progressBarHandler.postDelayed(progressBarRunnable, 1000);
};
}
private void runProgressBarHandler(boolean isPlaying) {
if (isPlaying) {
progressBarHandler.postDelayed(progressBarRunnable, 1000);
} else {
progressBarHandler.removeCallbacks(progressBarRunnable);
}
}
private void setHeaderBookmarksButton() {
if (Preferences.isSyncronizationEnabled()) {
playerBottomSheetViewModel.getPlayQueue().observeForever(new Observer<PlayQueue>() {
@Override
public void onChanged(PlayQueue playQueue) {
playerBottomSheetViewModel.getPlayQueue().removeObserver(this);
if (bind == null) return;
if (playQueue != null && !playQueue.getEntries().isEmpty()) {
int index = IntStream.range(0, playQueue.getEntries().size()).filter(ix -> playQueue.getEntries().get(ix).getId().equals(playQueue.getCurrent())).findFirst().orElse(-1);
if (index != -1) {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.VISIBLE);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, playQueue.getEntries(), index);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
});
}
} else {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnClickListener(null);
}
}
});
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setOnLongClickListener(v -> {
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
return true;
});
new Handler().postDelayed(() -> {
if (bind != null)
bind.playerHeaderLayout.playerHeaderBookmarkMediaButton.setVisibility(View.GONE);
}, Preferences.getSyncCountdownTimer() * 1000L);
}
}
}

View file

@ -0,0 +1,356 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.PlaybackParameters;
import androidx.media3.common.Player;
import androidx.media3.common.util.RepeatModeUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import androidx.viewpager2.widget.ViewPager2;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBinding;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.chip.Chip;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@UnstableApi
public class PlayerControllerFragment extends Fragment {
private static final String TAG = "PlayerCoverFragment";
private InnerFragmentPlayerControllerBinding bind;
private ViewPager2 playerMediaCoverViewPager;
private ToggleButton buttonFavorite;
private TextView playerMediaTitleLabel;
private TextView playerArtistNameLabel;
private Button playbackSpeedButton;
private ToggleButton skipSilenceToggleButton;
private Chip playerMediaExtension;
private TextView playerMediaBitrate;
private ImageView playerMediaTranscodingIcon;
private Chip playerMediaTranscodedExtension;
private TextView playerMediaTranscodedBitrate;
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = InnerFragmentPlayerControllerBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
initCoverLyricsSlideView();
initMediaListenable();
initArtistLabelButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
bindMediaController();
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
playerMediaCoverViewPager = bind.getRoot().findViewById(R.id.player_media_cover_view_pager);
buttonFavorite = bind.getRoot().findViewById(R.id.button_favorite);
playerMediaTitleLabel = bind.getRoot().findViewById(R.id.player_media_title_label);
playerArtistNameLabel = bind.getRoot().findViewById(R.id.player_artist_name_label);
playbackSpeedButton = bind.getRoot().findViewById(R.id.player_playback_speed_button);
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
bind.nowPlayingMediaControllerView.setPlayer(mediaBrowser);
setMediaControllerListener(mediaBrowser);
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaControllerListener(MediaBrowser mediaBrowser) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaBrowser.getMediaMetadata());
setMediaInfo(mediaBrowser.getMediaMetadata());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setMediaControllerUI(mediaBrowser);
setMetadata(mediaMetadata);
setMediaInfo(mediaMetadata);
}
});
}
private void setMetadata(MediaMetadata mediaMetadata) {
playerMediaTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
playerArtistNameLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
playerMediaTitleLabel.setSelected(true);
playerArtistNameLabel.setSelected(true);
}
private void setMediaInfo(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
: "Original";
playerMediaExtension.setText(extension);
if (bitrate.equals("Original")) {
playerMediaBitrate.setVisibility(View.GONE);
} else {
playerMediaBitrate.setVisibility(View.VISIBLE);
playerMediaBitrate.setText(bitrate);
}
}
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
: "Original";
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
} else {
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setText(transcodingExtension);
playerMediaTranscodedBitrate.setText(transcodingBitrate);
}
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
}
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
initPlaybackSpeedButton(mediaBrowser);
if (mediaBrowser.getMediaMetadata().extras != null) {
switch (mediaBrowser.getMediaMetadata().extras.getString("type", Constants.MEDIA_TYPE_MUSIC)) {
case Constants.MEDIA_TYPE_PODCAST:
bind.getRoot().setShowShuffleButton(false);
bind.getRoot().setShowRewindButton(true);
bind.getRoot().setShowPreviousButton(false);
bind.getRoot().setShowNextButton(false);
bind.getRoot().setShowFastForwardButton(true);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.VISIBLE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_RADIO:
bind.getRoot().setShowShuffleButton(false);
bind.getRoot().setShowRewindButton(false);
bind.getRoot().setShowPreviousButton(false);
bind.getRoot().setShowNextButton(false);
bind.getRoot().setShowFastForwardButton(false);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_MUSIC:
default:
bind.getRoot().setShowShuffleButton(true);
bind.getRoot().setShowRewindButton(false);
bind.getRoot().setShowPreviousButton(true);
bind.getRoot().setShowNextButton(true);
bind.getRoot().setShowFastForwardButton(false);
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
resetPlaybackParameters(mediaBrowser);
break;
}
}
}
private void initCoverLyricsSlideView() {
playerMediaCoverViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
playerMediaCoverViewPager.setAdapter(new PlayerControllerHorizontalPager(this));
playerMediaCoverViewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
if (position == 0) {
activity.setBottomSheetDraggableState(true);
} else if (position == 1) {
activity.setBottomSheetDraggableState(false);
}
}
});
}
private void initMediaListenable() {
playerBottomSheetViewModel.getLiveMedia().observe(getViewLifecycleOwner(), media -> {
if (media != null) {
buttonFavorite.setChecked(media.getStarred() != null);
buttonFavorite.setOnClickListener(v -> playerBottomSheetViewModel.setFavorite(requireContext(), media));
buttonFavorite.setOnLongClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, media);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
return true;
});
if (getActivity() != null) {
playerBottomSheetViewModel.refreshMediaInfo(requireActivity(), media);
}
}
});
}
private void initArtistLabelButton() {
playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
playerArtistNameLabel.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
activity.collapseBottomSheet();
});
}
});
}
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
playbackSpeedButton.setOnClickListener(view -> {
float currentSpeed = Preferences.getPlaybackSpeed();
if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_080) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_100));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_100) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_125));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_125));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_125);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_125) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_150));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_150));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_150);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_150) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_175));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_175));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_175);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_175) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_200));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_200));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_200);
} else if (currentSpeed == Constants.MEDIA_PLAYBACK_SPEED_200) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_080));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, Constants.MEDIA_PLAYBACK_SPEED_080));
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_080);
}
});
skipSilenceToggleButton.setOnClickListener(view -> {
Preferences.setSkipSilenceMode(!skipSilenceToggleButton.isChecked());
});
}
public void goToControllerPage() {
playerMediaCoverViewPager.setCurrentItem(0, false);
}
public void goToLyricsPage() {
playerMediaCoverViewPager.setCurrentItem(1, true);
}
private void setPlaybackParameters(MediaBrowser mediaBrowser) {
Button playbackSpeedButton = bind.getRoot().findViewById(R.id.player_playback_speed_button);
float currentSpeed = Preferences.getPlaybackSpeed();
boolean skipSilence = Preferences.isSkipSilenceMode();
mediaBrowser.setPlaybackParameters(new PlaybackParameters(currentSpeed));
playbackSpeedButton.setText(getString(R.string.player_playback_speed, currentSpeed));
// TODO Skippare il silenzio
skipSilenceToggleButton.setChecked(skipSilence);
}
private void resetPlaybackParameters(MediaBrowser mediaBrowser) {
mediaBrowser.setPlaybackParameters(new PlaybackParameters(Constants.MEDIA_PLAYBACK_SPEED_100));
// TODO Resettare lo skip del silenzio
}
}

View file

@ -0,0 +1,173 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerCoverBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@UnstableApi
public class PlayerCoverFragment extends Fragment {
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initOverlay();
initInnerButton();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
bindMediaController();
toggleOverlayVisibility(false);
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initOverlay() {
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
}
private void toggleOverlayVisibility(boolean isVisible) {
Transition transition = new Fade();
transition.setDuration(200);
transition.addTarget(bind.nowPlayingSongCoverButtonGroup);
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
}
private void initInnerButton() {
playerBottomSheetViewModel.getLiveMedia().observe(getViewLifecycleOwner(), song -> {
if (song != null && bind != null) {
bind.innerButtonTopLeft.setOnClickListener(view -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownload(song),
new Download(song)
);
});
bind.innerButtonTopRight.setOnClickListener(view -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
}
);
bind.innerButtonBottomLeft.setOnClickListener(view -> {
playerBottomSheetViewModel.getMediaInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), media -> {
MediaManager.enqueue(mediaBrowserListenableFuture, media, true);
});
});
bind.innerButtonBottomRight.setOnClickListener(view -> {
if (playerBottomSheetViewModel.savePlayQueue()) {
Snackbar.make(requireView(), "Salvato", Snackbar.LENGTH_LONG).show();
}
});
bind.innerButtonBottomRightAlternative.setOnClickListener(view -> {
if (getActivity() != null) {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.goToLyricsPage();
}
}
});
}
});
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
setMediaBrowserListener(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListener(MediaBrowser mediaBrowser) {
setCover(mediaBrowser.getMediaMetadata());
mediaBrowser.addListener(new Player.Listener() {
@Override
public void onMediaMetadataChanged(@NonNull MediaMetadata mediaMetadata) {
setCover(mediaMetadata);
toggleOverlayVisibility(false);
}
});
}
private void setCover(MediaMetadata mediaMetadata) {
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null)
.build()
.into(bind.nowPlayingSongCoverImageView);
}
}

View file

@ -0,0 +1,62 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerLyricsBinding;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
private InnerFragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerLyricsBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initLyrics();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initLyrics() {
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
if (bind != null) {
if (lyrics == null || lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
} else {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
}
}
});
}
}

View file

@ -0,0 +1,162 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerQueueBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.stream.Collectors;
@UnstableApi
public class PlayerQueueFragment extends Fragment implements ClickCallback {
private InnerFragmentPlayerQueueBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private PlayerSongQueueAdapter playerSongQueueAdapter;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerQueueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initQueueRecyclerView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
}
@Override
public void onResume() {
super.onResume();
setMediaBrowserListenableFuture();
updateNowPlayingItem();
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
private void initQueueRecyclerView() {
bind.playerQueueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playerQueueRecyclerView.setHasFixedSize(true);
playerSongQueueAdapter = new PlayerSongQueueAdapter(this);
bind.playerQueueRecyclerView.setAdapter(playerSongQueueAdapter);
playerBottomSheetViewModel.getQueueSong().observe(getViewLifecycleOwner(), queue -> {
if (queue != null) {
playerSongQueueAdapter.setItems(queue.stream().map(item -> (Child) item).collect(Collectors.toList()));
}
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
int fromPosition = -1;
int toPosition = -1;
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
if (originalPosition == -1) {
originalPosition = viewHolder.getBindingAdapterPosition();
}
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
/*
* Per spostare un elemento nella coda devo:
* - Spostare graficamente la traccia da una posizione all'altra con Collections.swap()
* - Spostare nel db la traccia, tramite QueueRepository
* - Notificare il Service dell'avvenuto spostamento con MusicPlayerRemote.moveSong()
*
* In onMove prendo la posizione di inizio e fine, ma solo al rilascio dell'elemento procedo allo spostamento
* In questo modo evito che ad ogni cambio di posizione vada a riscrivere nel db
* Al rilascio dell'elemento chiamo il metodo clearView()
*/
Collections.swap(playerSongQueueAdapter.getItems(), fromPosition, toPosition);
recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition);
return false;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
if (originalPosition != -1 && fromPosition != -1 && toPosition != -1) {
MediaManager.swap(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), originalPosition, toPosition);
}
originalPosition = -1;
fromPosition = -1;
toPosition = -1;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
MediaManager.remove(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), viewHolder.getBindingAdapterPosition());
viewHolder.getBindingAdapter().notifyDataSetChanged();
}
}).attachToRecyclerView(bind.playerQueueRecyclerView);
}
private void updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged();
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
}
}

View file

@ -0,0 +1,186 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlaylistCatalogueBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PlaylistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaylistCatalogueViewModel;
@UnstableApi
public class PlaylistCatalogueFragment extends Fragment implements ClickCallback {
private FragmentPlaylistCatalogueBinding bind;
private MainActivity activity;
private PlaylistCatalogueViewModel playlistCatalogueViewModel;
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPlaylistCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playlistCatalogueViewModel = new ViewModelProvider(requireActivity()).get(PlaylistCatalogueViewModel.class);
init();
initAppBar();
initPlaylistCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.PLAYLIST_ALL) != null) {
playlistCatalogueViewModel.setType(Constants.PLAYLIST_ALL);
} else if (requireArguments().getString(Constants.PLAYLIST_DOWNLOADED) != null) {
playlistCatalogueViewModel.setType(Constants.PLAYLIST_DOWNLOADED);
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.playlist_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initPlaylistCatalogueView() {
bind.playlistCatalogueRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistCatalogueRecyclerView.setHasFixedSize(true);
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
bind.playlistCatalogueRecyclerView.setAdapter(playlistHorizontalAdapter);
if (getActivity() != null) {
playlistCatalogueViewModel.getPlaylistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists != null) playlistHorizontalAdapter.setItems(playlists);
});
}
bind.playlistCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
bind.playlistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_playlist_popup_menu));
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
playlistHorizontalAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
private void showPopupMenu(View view, int menuResource) {
PopupMenu popup = new PopupMenu(requireContext(), view);
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
popup.setOnMenuItemClickListener(menuItem -> {
if (menuItem.getItemId() == R.id.menu_playlist_sort_name) {
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_NAME);
return true;
} else if (menuItem.getItemId() == R.id.menu_playlist_sort_random) {
playlistHorizontalAdapter.sort(Constants.GENRE_ORDER_BY_RANDOM);
return true;
}
return false;
});
popup.show();
}
@Override
public void onPlaylistClick(Bundle bundle) {
bundle.putBoolean("is_offline", false);
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
hideKeyboard(requireView());
}
}

View file

@ -0,0 +1,197 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPlaylistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PlaylistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class PlaylistPageFragment extends Fragment implements ClickCallback {
private FragmentPlaylistPageBinding bind;
private MainActivity activity;
private PlaylistPageViewModel playlistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.playlist_page_menu, menu);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPlaylistPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playlistPageViewModel = new ViewModelProvider(requireActivity()).get(PlaylistPageViewModel.class);
init();
initAppBar();
initMusicButton();
initBackCover();
initSongsView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_playlist) {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (isVisible() && getActivity() != null) {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(child -> {
Download toDownload = new Download(child);
toDownload.setPlaylistId(playlistPageViewModel.getPlaylist().getId());
toDownload.setPlaylistName(playlistPageViewModel.getPlaylist().getName());
return toDownload;
}).collect(Collectors.toList())
);
}
});
return true;
}
return false;
}
private void init() {
playlistPageViewModel.setPlaylist(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.animToolbar.setTitle(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
bind.playlistNameLabel.setText(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, playlistPageViewModel.getPlaylist().getSongCount()));
bind.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(playlistPageViewModel.getPlaylist().getDuration(), false)));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
}
private void initMusicButton() {
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
bind.playlistPagePlayButton.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
bind.playlistPageShuffleButton.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
});
}
});
}
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), playlistPageViewModel.getPlaylist().getCoverArtId())
.build()
.into(bind.playlistCoverImageView);
}
private void initSongsView() {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,155 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.SearchView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelCatalogueBinding;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastChannelCatalogueAdapter;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelCatalogueViewModel;
@OptIn(markerClass = UnstableApi.class)
public class PodcastChannelCatalogueFragment extends Fragment implements ClickCallback {
private static final String TAG = "PodcastChannelCatalogue";
private FragmentPodcastChannelCatalogueBinding bind;
private MainActivity activity;
private PodcastChannelCatalogueViewModel podcastChannelCatalogueViewModel;
private PodcastChannelCatalogueAdapter podcastChannelCatalogueAdapter;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPodcastChannelCatalogueBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastChannelCatalogueViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelCatalogueViewModel.class);
initAppBar();
initPodcastChannelCatalogueView();
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
bind.toolbar.setNavigationOnClickListener(v -> {
hideKeyboard(v);
activity.navController.navigateUp();
});
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.podcastChannelInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(R.string.podcast_channel_catalogue_title);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
@SuppressLint("ClickableViewAccessibility")
private void initPodcastChannelCatalogueView() {
bind.podcastChannelCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.podcastChannelCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.podcastChannelCatalogueRecyclerView.setHasFixedSize(true);
podcastChannelCatalogueAdapter = new PodcastChannelCatalogueAdapter(this);
podcastChannelCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.podcastChannelCatalogueRecyclerView.setAdapter(podcastChannelCatalogueAdapter);
podcastChannelCatalogueViewModel.getPodcastChannels(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums != null) {
podcastChannelCatalogueAdapter.setItems(albums);
}
});
bind.podcastChannelCatalogueRecyclerView.setOnTouchListener((v, event) -> {
hideKeyboard(v);
return false;
});
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView();
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
searchView.clearFocus();
return false;
}
@Override
public boolean onQueryTextChange(String newText) {
podcastChannelCatalogueAdapter.getFilter().filter(newText);
return false;
}
});
searchView.setPadding(-32, 0, 0, 0);
}
private void hideKeyboard(View view) {
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
@Override
public void onPodcastChannelClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastChannelPageFragment, bundle);
hideKeyboard(requireView());
}
@Override
public void onPodcastChannelLongClick(Bundle bundle) {
// Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,158 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentPodcastChannelPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.PodcastEpisodeAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@UnstableApi
public class PodcastChannelPageFragment extends Fragment implements ClickCallback {
private FragmentPodcastChannelPageBinding bind;
private MainActivity activity;
private PodcastChannelPageViewModel podcastChannelPageViewModel;
private PodcastEpisodeAdapter podcastEpisodeAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentPodcastChannelPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
podcastChannelPageViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelPageViewModel.class);
init();
initAppBar();
initPodcastChannelInfo();
initPodcastChannelEpisodesView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
podcastChannelPageViewModel.setPodcastChannel(requireArguments().getParcelable(Constants.PODCAST_CHANNEL_OBJECT));
}
private void initAppBar() {
activity.setSupportActionBar(bind.animToolbar);
if (activity.getSupportActionBar() != null)
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
}
private void initPodcastChannelInfo() {
String normalizePodcastChannelDescription = MusicUtil.forceReadableString(podcastChannelPageViewModel.getPodcastChannel().getDescription());
if (bind != null)
bind.podcastChannelDescriptionTextView.setVisibility(!normalizePodcastChannelDescription.trim().isEmpty() ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), podcastChannelPageViewModel.getPodcastChannel().getCoverArtId())
.build()
.into(bind.podcastChannelBackdropImageView);
if (bind != null)
bind.podcastChannelDescriptionTextView.setText(normalizePodcastChannelDescription);
}
private void initPodcastChannelEpisodesView() {
bind.podcastEpisodesRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.podcastEpisodesRecyclerView.addItemDecoration(UIUtil.getDividerItemDecoration(requireContext()));
podcastEpisodeAdapter = new PodcastEpisodeAdapter(this);
bind.podcastEpisodesRecyclerView.setAdapter(podcastEpisodeAdapter);
podcastChannelPageViewModel.getPodcastChannelEpisodes().observe(getViewLifecycleOwner(), channels -> {
if (channels == null) {
if (bind != null)
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.podcastChannelPageEpisodesSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.podcastChannelPageEpisodesPlaceholder.placeholder.setVisibility(View.GONE);
if (!channels.isEmpty() && channels.get(0) != null && channels.get(0).getEpisodes() != null) {
List<PodcastEpisode> availableEpisode = channels.get(0).getEpisodes().stream().filter(podcastEpisode -> Objects.equals(podcastEpisode.getStatus(), "completed")).collect(Collectors.toList());
if (bind != null) {
bind.podcastEpisodesRecyclerView.setVisibility(availableEpisode.isEmpty() ? View.GONE : View.VISIBLE);
bind.podcastEpisodesAvailabilityTextView.setVisibility(availableEpisode.isEmpty() ? View.VISIBLE : View.GONE);
}
podcastEpisodeAdapter.setItems(availableEpisode);
}
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onPodcastEpisodeClick(Bundle bundle) {
MediaManager.startPodcast(mediaBrowserListenableFuture, bundle.getParcelable(Constants.PODCAST_OBJECT));
activity.setBottomSheetInPeek(true);
}
@Override
public void onPodcastEpisodeLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.podcastEpisodeBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,260 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentSearchBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
import java.util.Collections;
@UnstableApi
public class SearchFragment extends Fragment implements ClickCallback {
private FragmentSearchBinding bind;
private MainActivity activity;
private SearchViewModel searchViewModel;
private ArtistAdapter artistAdapter;
private AlbumAdapter albumAdapter;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSearchBinding.inflate(inflater, container, false);
View view = bind.getRoot();
searchViewModel = new ViewModelProvider(requireActivity()).get(SearchViewModel.class);
initSearchResultView();
initSearchView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onResume() {
super.onResume();
inputFocus();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void initSearchResultView() {
// Artists
bind.searchResultArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.searchResultArtistRecyclerView.setHasFixedSize(true);
artistAdapter = new ArtistAdapter(this, false, false);
bind.searchResultArtistRecyclerView.setAdapter(artistAdapter);
CustomLinearSnapHelper artistSnapHelper = new CustomLinearSnapHelper();
artistSnapHelper.attachToRecyclerView(bind.searchResultArtistRecyclerView);
// Albums
bind.searchResultAlbumRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.searchResultAlbumRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumAdapter(this);
bind.searchResultAlbumRecyclerView.setAdapter(albumAdapter);
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.searchResultAlbumRecyclerView);
// Songs
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
}
private void initSearchView() {
if (isQueryValid(searchViewModel.getQuery())) {
search(searchViewModel.getQuery());
}
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
setSuggestions();
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
} else {
setSuggestions();
}
});
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
@Override
public void onSuggestionPicked(SuggestionItem suggestion) {
search(suggestion.getItemModel().getText());
}
@Override
public void onSuggestionRemoved(SuggestionItem suggestion) {
}
});
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
if (isQueryValid(query)) {
searchView.collapse();
search(query);
} else {
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
}
});
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
@Override
public void onSuggestionPicked(SuggestionItem suggestion) {
search(suggestion.getItemModel().getText());
}
@Override
public void onSuggestionRemoved(SuggestionItem suggestion) {
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
}
});
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
}
private void setSuggestions() {
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
}
public void search(String query) {
searchViewModel.setQuery(query);
bind.persistentSearchView.setInputQuery(query);
performSearch(query);
}
private void performSearch(String query) {
searchViewModel.search(query).observe(getViewLifecycleOwner(), result -> {
if (bind != null) {
if (result.getArtists() != null) {
bind.searchArtistSector.setVisibility(!result.getArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistAdapter.setItems(result.getArtists());
} else {
artistAdapter.setItems(Collections.emptyList());
bind.searchArtistSector.setVisibility(View.GONE);
}
if (result.getAlbums() != null) {
bind.searchAlbumSector.setVisibility(!result.getAlbums().isEmpty() ? View.VISIBLE : View.GONE);
albumAdapter.setItems(result.getAlbums());
} else {
albumAdapter.setItems(Collections.emptyList());
bind.searchAlbumSector.setVisibility(View.GONE);
}
if (result.getSongs() != null) {
bind.searchSongSector.setVisibility(!result.getSongs().isEmpty() ? View.VISIBLE : View.GONE);
songHorizontalAdapter.setItems(result.getSongs());
} else {
songHorizontalAdapter.setItems(Collections.emptyList());
bind.searchSongSector.setVisibility(View.GONE);
}
}
});
bind.searchResultLayout.setVisibility(View.VISIBLE);
}
private boolean isQueryValid(String query) {
return !query.equals("") && query.trim().length() > 2;
}
private void inputFocus() {
bind.persistentSearchView.expand();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
}
@Override
public void onAlbumLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumBottomSheetDialog, bundle);
}
@Override
public void onArtistClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
}
@Override
public void onArtistLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.artistBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,144 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.Intent;
import android.media.audiofx.AudioEffect;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
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.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.interfaces.ScanCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
@OptIn(markerClass = UnstableApi.class)
public class SettingsFragment extends PreferenceFragmentCompat {
private static final String TAG = "SettingsFragment";
private MainActivity activity;
private SettingViewModel settingViewModel;
private ActivityResultLauncher<Intent> someActivityResultLauncher;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
someActivityResultLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
});
}
@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();
findPreference("logout").setOnPreferenceClickListener(preference -> {
activity.quit();
return true;
});
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) {
getScanStatus();
}
});
return true;
});
findPreference("equalizer").setOnPreferenceClickListener(preference -> {
Intent intent = new Intent(AudioEffect.ACTION_DISPLAY_AUDIO_EFFECT_CONTROL_PANEL);
someActivityResultLauncher.launch(intent);
return true;
});
findPreference("sync_starred_tracks_for_offline_use").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
StarredSyncDialog dialog = new StarredSyncDialog();
dialog.show(activity.getSupportFragmentManager(), null);
}
}
return true;
});
}
@Override
public void onStop() {
super.onStop();
activity.setBottomSheetVisibility(true);
}
@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 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("Scanning: counting " + count + " tracks");
if (isScanning) getScanStatus();
}
});
}
}

View file

@ -0,0 +1,208 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.core.view.ViewCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentSongListPageBinding;
import com.cappielloantonio.tempo.helper.recyclerview.PaginationScrollListener;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
@UnstableApi
public class SongListPageFragment extends Fragment implements ClickCallback {
private static final String TAG = "SongListPageFragment";
private FragmentSongListPageBinding bind;
private MainActivity activity;
private SongListPageViewModel songListPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private boolean isLoading = true;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
bind = FragmentSongListPageBinding.inflate(inflater, container, false);
View view = bind.getRoot();
songListPageViewModel = new ViewModelProvider(requireActivity()).get(SongListPageViewModel.class);
init();
initAppBar();
initButtons();
initSongListView();
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void init() {
if (requireArguments().getString(Constants.MEDIA_RECENTLY_PLAYED) != null) {
songListPageViewModel.title = Constants.MEDIA_RECENTLY_PLAYED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_recently_played);
bind.pageTitleLabel.setText(R.string.song_list_page_recently_played);
} else if (requireArguments().getString(Constants.MEDIA_MOST_PLAYED) != null) {
songListPageViewModel.title = Constants.MEDIA_MOST_PLAYED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_most_played);
bind.pageTitleLabel.setText(R.string.song_list_page_most_played);
} else if (requireArguments().getString(Constants.MEDIA_RECENTLY_ADDED) != null) {
songListPageViewModel.title = Constants.MEDIA_RECENTLY_ADDED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_recently_added);
bind.pageTitleLabel.setText(R.string.song_list_page_recently_added);
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRE) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_GENRE;
songListPageViewModel.genre = requireArguments().getParcelable(Constants.GENRE_OBJECT);
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.genre.getGenre());
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.genre.getGenre()));
} else if (requireArguments().getString(Constants.MEDIA_BY_ARTIST) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_ARTIST;
songListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName()));
bind.pageTitleLabel.setText(getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName())));
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRES) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_GENRES;
songListPageViewModel.filters = requireArguments().getStringArrayList("filters_list");
songListPageViewModel.filterNames = requireArguments().getStringArrayList("filter_name_list");
songListPageViewModel.toolbarTitle = songListPageViewModel.getFiltersTitle();
bind.pageTitleLabel.setText(songListPageViewModel.getFiltersTitle());
} else if (requireArguments().getString(Constants.MEDIA_BY_YEAR) != null) {
songListPageViewModel.title = Constants.MEDIA_BY_YEAR;
songListPageViewModel.year = requireArguments().getInt("year_object");
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_year, songListPageViewModel.year);
bind.pageTitleLabel.setText(getString(R.string.song_list_page_year, songListPageViewModel.year));
} else if (requireArguments().getString(Constants.MEDIA_STARRED) != null) {
songListPageViewModel.title = Constants.MEDIA_STARRED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_starred);
bind.pageTitleLabel.setText(R.string.song_list_page_starred);
} else if (requireArguments().getString(Constants.MEDIA_DOWNLOADED) != null) {
songListPageViewModel.title = Constants.MEDIA_DOWNLOADED;
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_downloaded);
bind.pageTitleLabel.setText(getString(R.string.song_list_page_downloaded));
} else if (requireArguments().getParcelable(Constants.ALBUM_OBJECT) != null) {
songListPageViewModel.album = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
songListPageViewModel.title = Constants.MEDIA_FROM_ALBUM;
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.album.getName());
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.album.getName()));
}
}
private void initAppBar() {
activity.setSupportActionBar(bind.toolbar);
if (activity.getSupportActionBar() != null) {
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
}
if (bind != null)
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
if (bind != null)
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
bind.toolbar.setTitle(songListPageViewModel.toolbarTitle);
} else {
bind.toolbar.setTitle(R.string.empty_string);
}
});
}
private void initButtons() {
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null) {
bind.songListShuffleImageView.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
activity.setBottomSheetInPeek(true);
});
}
});
}
private void initSongListView() {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false;
songHorizontalAdapter.setItems(songs);
});
bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) {
@Override
protected void loadMoreItems() {
isLoading = true;
songListPageViewModel.getSongsByPage(getViewLifecycleOwner());
}
@Override
public boolean isLoading() {
return isLoading;
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
@Override
public void onMediaClick(Bundle bundle) {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
activity.setBottomSheetInPeek(true);
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
}

View file

@ -0,0 +1,202 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.MediaCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@UnstableApi
public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private AlbumBottomSheetViewModel albumBottomSheetViewModel;
private AlbumID3 album;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_album_dialog, container, false);
album = this.requireArguments().getParcelable(Constants.ALBUM_OBJECT);
albumBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(AlbumBottomSheetViewModel.class);
albumBottomSheetViewModel.setAlbum(album);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId())
.build()
.into(coverAlbum);
TextView titleAlbum = view.findViewById(R.id.album_title_text_view);
titleAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getName()));
titleAlbum.setSelected(true);
TextView artistAlbum = view.findViewById(R.id.album_artist_text_view);
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(albumBottomSheetViewModel.getAlbum().getStarred()));
favoriteToggle.setOnClickListener(v -> {
albumBottomSheetViewModel.setFavorite();
dismissBottomSheet();
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
AlbumRepository albumRepository = new AlbumRepository();
albumRepository.getInstantMix(album, 20, new MediaCallback() {
@Override
public void onError(Exception exception) {
exception.printStackTrace();
}
@Override
public void onLoadMedia(List<?> media) {
if (media.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}
dismissBottomSheet();
}
});
});
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setOnClickListener(v -> {
AlbumRepository albumRepository = new AlbumRepository();
albumRepository.getAlbumTracks(album.getId()).observe(getViewLifecycleOwner(), songs -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
});
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
}));
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
}));
TextView downloadAll = view.findViewById(R.id.download_all_text_view);
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
downloadAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
dismissBottomSheet();
});
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
} else {
removeAll.setVisibility(View.GONE);
}
});
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
} else {
Toast.makeText(requireContext(), getString(R.string.album_error_retrieving_artist), Toast.LENGTH_SHORT).show();
}
dismissBottomSheet();
}));
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,136 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.ArtistBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "AlbumBottomSheetDialog";
private ArtistBottomSheetViewModel artistBottomSheetViewModel;
private ArtistID3 artist;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_artist_dialog, container, false);
artist = this.requireArguments().getParcelable(Constants.ARTIST_OBJECT);
artistBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ArtistBottomSheetViewModel.class);
artistBottomSheetViewModel.setArtist(artist);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
// TODO Utilizzare il viewmodel come tramite ed evitare le chiamate dirette
private void init(View view) {
ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId())
.build()
.into(coverArtist);
TextView nameArtist = view.findViewById(R.id.song_title_text_view);
nameArtist.setText(MusicUtil.getReadableString(artistBottomSheetViewModel.getArtist().getName()));
nameArtist.setSelected(true);
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(artistBottomSheetViewModel.getArtist().getStarred()));
favoriteToggle.setOnClickListener(v -> {
artistBottomSheetViewModel.setFavorite();
dismissBottomSheet();
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
ArtistRepository artistRepository = new ArtistRepository();
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}
dismissBottomSheet();
});
});
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setOnClickListener(v -> {
ArtistRepository artistRepository = new ArtistRepository();
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
} else {
Toast.makeText(requireContext(), getString(R.string.artist_error_retrieving_tracks), Toast.LENGTH_SHORT).show();
}
dismissBottomSheet();
});
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,96 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastChannelBottomSheetViewModel podcastChannelBottomSheetViewModel;
private PodcastChannel podcastChannel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_podcast_channel_dialog, container, false);
podcastChannel = requireArguments().getParcelable(Constants.PODCAST_CHANNEL_OBJECT);
podcastChannelBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelBottomSheetViewModel.class);
podcastChannelBottomSheetViewModel.setPodcastChannel(podcastChannel);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId())
.build()
.into(coverPodcast);
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
titlePodcast.setText(MusicUtil.getReadableString(podcastChannelBottomSheetViewModel.getPodcastChannel().getTitle()));
TextView delete = view.findViewById(R.id.delete_text_view);
delete.setOnClickListener(v -> {
podcastChannelBottomSheetViewModel.deletePodcastChannel();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,155 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.PodcastEpisodeBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private PodcastEpisodeBottomSheetViewModel podcastEpisodeBottomSheetViewModel;
private PodcastEpisode podcastEpisode;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_podcast_episode_dialog, container, false);
podcastEpisode = requireArguments().getParcelable(Constants.PODCAST_OBJECT);
podcastEpisodeBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PodcastEpisodeBottomSheetViewModel.class);
podcastEpisodeBottomSheetViewModel.setPodcastEpisode(podcastEpisode);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId())
.build()
.into(coverPodcast);
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
titlePodcast.setText(MusicUtil.getReadableString(podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getTitle()));
titlePodcast.setSelected(true);
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
// TODO
// MediaManager.enqueue(mediaBrowserListenableFuture, podcast, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
// TODO
// MediaManager.enqueue(mediaBrowserListenableFuture, podcast, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView download = view.findViewById(R.id.download_text_view);
download.setOnClickListener(v -> {
// TODO
/* DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapMediaItem(podcast, false),
MappingUtil.mapDownload(podcast, null, null)
); */
dismissBottomSheet();
});
TextView remove = view.findViewById(R.id.remove_text_view);
remove.setOnClickListener(v -> {
// TODO
/* DownloadUtil.getDownloadTracker(requireContext()).remove(
MappingUtil.mapMediaItem(podcast, false),
MappingUtil.mapDownload(podcast, null, null)
); */
dismissBottomSheet();
});
initDownloadUI(download, remove);
TextView delete = view.findViewById(R.id.delete_text_view);
delete.setOnClickListener(v -> {
podcastEpisodeBottomSheetViewModel.deletePodcastEpisode();
dismissBottomSheet();
});
TextView goToChannel = view.findViewById(R.id.go_to_channel_text_view);
goToChannel.setOnClickListener(v -> {
Toast.makeText(requireContext(), "Open the channel", Toast.LENGTH_SHORT).show();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initDownloadUI(TextView download, TextView remove) {
// TODO
/* if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(MappingUtil.mapMediaItem(podcast, false))) {
download.setVisibility(View.GONE);
remove.setVisibility(View.VISIBLE);
} else {
download.setVisibility(View.VISIBLE);
remove.setVisibility(View.GONE);
} */
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,234 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.fragment.NavHostFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.PlaylistChooserDialog;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "SongBottomSheetDialog";
private SongBottomSheetViewModel songBottomSheetViewModel;
private Child song;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_song_dialog, container, false);
song = requireArguments().getParcelable(Constants.TRACK_OBJECT);
songBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(SongBottomSheetViewModel.class);
songBottomSheetViewModel.setSong(song);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void init(View view) {
ImageView coverSong = view.findViewById(R.id.song_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId())
.build()
.into(coverSong);
TextView titleSong = view.findViewById(R.id.song_title_text_view);
titleSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getTitle()));
titleSong.setSelected(true);
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
favoriteToggle.setChecked(Boolean.TRUE.equals(songBottomSheetViewModel.getSong().getStarred()));
favoriteToggle.setOnClickListener(v -> {
songBottomSheetViewModel.setFavorite(requireContext());
dismissBottomSheet();
});
favoriteToggle.setOnLongClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
return true;
});
TextView playRadio = view.findViewById(R.id.play_radio_text_view);
playRadio.setOnClickListener(v -> {
MediaManager.startQueue(mediaBrowserListenableFuture, song);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
songBottomSheetViewModel.getInstantMix(getViewLifecycleOwner(), song).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
dismissBottomSheet();
return;
}
if (songs.size() > 0) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
dismissBottomSheet();
}
});
});
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, song, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, song, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView rate = view.findViewById(R.id.rate_text_view);
rate.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
RatingDialog dialog = new RatingDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
});
TextView download = view.findViewById(R.id.download_text_view);
download.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownload(song),
new Download(song)
);
dismissBottomSheet();
});
TextView remove = view.findViewById(R.id.remove_text_view);
remove.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(
MappingUtil.mapDownload(song),
new Download(song)
);
dismissBottomSheet();
});
initDownloadUI(download, remove);
TextView addToPlaylist = view.findViewById(R.id.add_to_playlist_text_view);
addToPlaylist.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, song);
PlaylistChooserDialog dialog = new PlaylistChooserDialog();
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);
dismissBottomSheet();
});
TextView goToAlbum = view.findViewById(R.id.go_to_album_text_view);
goToAlbum.setOnClickListener(v -> songBottomSheetViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
if (album != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
NavHostFragment.findNavController(this).navigate(R.id.albumPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.song_bottom_sheet_error_retrieving_album), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}));
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> songBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.ARTIST_OBJECT, artist);
NavHostFragment.findNavController(this).navigate(R.id.artistPageFragment, bundle);
} else
Toast.makeText(requireContext(), getString(R.string.song_bottom_sheet_error_retrieving_artist), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}));
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initDownloadUI(TextView download, TextView remove) {
if (DownloadUtil.getDownloadTracker(requireContext()).isDownloaded(song.getId())) {
download.setVisibility(View.GONE);
remove.setVisibility(View.VISIBLE);
} else {
download.setVisibility(View.VISIBLE);
remove.setVisibility(View.GONE);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.media3.common.util.UnstableApi;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
@OptIn(markerClass = UnstableApi.class)
public class HomePager extends FragmentStateAdapter {
private static final String TAG = "HomePager";
private final List<Fragment> fragments = new ArrayList<>();
private final List<String> titles = new ArrayList<>();
private final List<Integer> icons = new ArrayList<>();
public HomePager(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public Fragment createFragment(int position) {
return fragments.get(position);
}
@Override
public int getItemCount() {
return fragments.size();
}
public void addFragment(Fragment fragment, String title, int drawable) {
fragments.add(fragment);
titles.add(title);
icons.add(drawable);
}
public String getPageTitle(int position) {
return titles.get(position);
}
public Integer getPageIcon(int position) {
return icons.get(position);
}
}

View file

@ -0,0 +1,34 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.cappielloantonio.tempo.ui.fragment.PlayerCoverFragment;
import com.cappielloantonio.tempo.ui.fragment.PlayerLyricsFragment;
public class PlayerControllerHorizontalPager extends FragmentStateAdapter {
private static final String TAG = "PlayerControllerHorizontalPager";
public PlayerControllerHorizontalPager(@NonNull Fragment fragment) {
super(fragment);
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
return new PlayerCoverFragment();
case 1:
return new PlayerLyricsFragment();
}
return new PlayerCoverFragment();
}
@Override
public int getItemCount() {
return 2;
}
}

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.ui.fragment.pager;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.cappielloantonio.tempo.ui.fragment.PlayerControllerFragment;
import com.cappielloantonio.tempo.ui.fragment.PlayerQueueFragment;
import java.util.HashMap;
public class PlayerControllerVerticalPager extends FragmentStateAdapter {
private final HashMap<Integer, Fragment> maps;
public PlayerControllerVerticalPager(@NonNull Fragment fragment) {
super(fragment);
this.maps = new HashMap<>();
}
@NonNull
@Override
public Fragment createFragment(int position) {
switch (position) {
case 0:
Fragment playerControllerFragment = new PlayerControllerFragment();
maps.put(position, playerControllerFragment);
return playerControllerFragment;
case 1:
Fragment playerQueueFragment = new PlayerQueueFragment();
maps.put(position, playerQueueFragment);
return playerQueueFragment;
}
Fragment playerControllerFragment = new PlayerControllerFragment();
maps.put(position, playerControllerFragment);
return playerControllerFragment;
}
@Override
public int getItemCount() {
return 2;
}
public Fragment getRegisteredFragment(int position) {
return maps.get(position);
}
}