mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
feat: folder navigation
This commit is contained in:
parent
e85d7f9198
commit
24d2d201ad
29 changed files with 1238 additions and 9 deletions
|
|
@ -39,4 +39,16 @@ public interface ClickCallback {
|
|||
default void onInternetRadioStationClick(Bundle bundle) {}
|
||||
|
||||
default void onInternetRadioStationLongClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicFolderClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicFolderLongClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicDirectoryLongClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicIndexClick(Bundle bundle) {}
|
||||
|
||||
default void onMusicIndexLongClick(Bundle bundle) {}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.cappielloantonio.play.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.play.App;
|
||||
import com.cappielloantonio.play.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.play.subsonic.models.Directory;
|
||||
import com.cappielloantonio.play.subsonic.models.Indexes;
|
||||
import com.cappielloantonio.play.subsonic.models.MusicFolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class DirectoryRepository {
|
||||
private static final String TAG = "DirectoryRepository";
|
||||
|
||||
public MutableLiveData<List<MusicFolder>> getMusicFolders() {
|
||||
MutableLiveData<List<MusicFolder>> liveMusicFolders = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicFolders()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getMusicFolders() != null) {
|
||||
liveMusicFolders.setValue(response.body().getSubsonicResponse().getMusicFolders().getMusicFolders());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return liveMusicFolders;
|
||||
}
|
||||
|
||||
public MutableLiveData<Indexes> getIndexes(String musicFolderId, Long ifModifiedSince) {
|
||||
MutableLiveData<Indexes> liveIndexes = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getIndexes(musicFolderId, ifModifiedSince)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getIndexes() != null) {
|
||||
liveIndexes.setValue(response.body().getSubsonicResponse().getIndexes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return liveIndexes;
|
||||
}
|
||||
|
||||
public MutableLiveData<Directory> getMusicDirectory(String id) {
|
||||
MutableLiveData<Directory> liveMusicDirectory = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicDirectory(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getDirectory() != null) {
|
||||
liveMusicDirectory.setValue(response.body().getSubsonicResponse().getDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
return liveMusicDirectory;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,9 +24,9 @@ public class BrowsingClient {
|
|||
return browsingService.getMusicFolders(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getIndexes() {
|
||||
public Call<ApiResponse> getIndexes(String musicFolderId, Long ifModifiedSince) {
|
||||
Log.d(TAG, "getIndexes()");
|
||||
return browsingService.getIndexes(subsonic.getParams());
|
||||
return browsingService.getIndexes(subsonic.getParams(), musicFolderId, ifModifiedSince);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getMusicDirectory(String id) {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public interface BrowsingService {
|
|||
Call<ApiResponse> getMusicFolders(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getIndexes")
|
||||
Call<ApiResponse> getIndexes(@QueryMap Map<String, String> params);
|
||||
Call<ApiResponse> getIndexes(@QueryMap Map<String, String> params, @Query("musicFolderId") String musicFolderId, @Query("ifModifiedSince") Long ifModifiedSince);
|
||||
|
||||
@GET("getMusicDirectory")
|
||||
Call<ApiResponse> getMusicDirectory(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import java.util.*
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
class Artist {
|
||||
@Parcelize
|
||||
class Artist : Parcelable {
|
||||
var id: String? = null
|
||||
var name: String? = null
|
||||
var starred: Date? = null
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import java.util.*
|
||||
import android.os.Parcelable
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
class Directory {
|
||||
@Parcelize
|
||||
class Directory : Parcelable {
|
||||
@SerializedName("child")
|
||||
var children: List<Child>? = null
|
||||
var id: String? = null
|
||||
@SerializedName("parent")
|
||||
var parentId: String? = null
|
||||
var name: String? = null
|
||||
var starred: Date? = null
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class Index {
|
||||
@SerializedName("artist")
|
||||
var artists: List<Artist>? = null
|
||||
var name: String? = null
|
||||
}
|
||||
|
|
@ -1,7 +1,10 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class Indexes {
|
||||
var shortcuts: List<Artist>? = null
|
||||
@SerializedName("index")
|
||||
var indices: List<Index>? = null
|
||||
var children: List<Child>? = null
|
||||
var lastModified: Long = 0
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
class MusicFolder {
|
||||
var id = 0
|
||||
import android.os.Parcelable
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class MusicFolder : Parcelable {
|
||||
var id: String? = null
|
||||
var name: String? = null
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
package com.cappielloantonio.play.subsonic.models
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class MusicFolders {
|
||||
@SerializedName("musicFolder")
|
||||
var musicFolders: List<MusicFolder>? = null
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.cappielloantonio.play.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.play.databinding.ItemLibraryMusicDirectoryBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.Child;
|
||||
import com.cappielloantonio.play.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());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.musicDirectoryMoreButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_DIRECTORY_OBJECT, children.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onMusicDirectoryLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.cappielloantonio.play.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.play.databinding.ItemLibraryMusicFolderBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.MusicFolder;
|
||||
import com.cappielloantonio.play.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());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.musicFolderMoreButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_FOLDER_OBJECT, musicFolders.get(getBindingAdapterPosition()));
|
||||
click.onMusicFolderClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_FOLDER_OBJECT, musicFolders.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onMusicFolderLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.cappielloantonio.play.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.play.databinding.ItemLibraryMusicIndexBinding;
|
||||
import com.cappielloantonio.play.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.Artist;
|
||||
import com.cappielloantonio.play.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());
|
||||
itemView.setOnLongClickListener(v -> onLongClick());
|
||||
|
||||
item.musicIndexMoreButton.setOnClickListener(v -> onLongClick());
|
||||
}
|
||||
|
||||
public void onClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
|
||||
click.onMusicIndexClick(bundle);
|
||||
}
|
||||
|
||||
private boolean onLongClick() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.MUSIC_INDEX_OBJECT, artists.get(getBindingAdapterPosition()));
|
||||
|
||||
click.onMusicIndexLongClick(bundle);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
package com.cappielloantonio.play.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.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.play.R;
|
||||
import com.cappielloantonio.play.databinding.FragmentDirectoryBinding;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.service.MediaManager;
|
||||
import com.cappielloantonio.play.service.MediaService;
|
||||
import com.cappielloantonio.play.subsonic.models.Artist;
|
||||
import com.cappielloantonio.play.subsonic.models.Child;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.ui.adapter.MusicDirectoryAdapter;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.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());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicDirectoryLongClick(Bundle bundle) {
|
||||
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.cappielloantonio.play.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.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.play.R;
|
||||
import com.cappielloantonio.play.databinding.FragmentIndexBinding;
|
||||
import com.cappielloantonio.play.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.play.subsonic.models.MusicFolder;
|
||||
import com.cappielloantonio.play.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.play.ui.adapter.MusicIndexAdapter;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
import com.cappielloantonio.play.util.IndexUtil;
|
||||
import com.cappielloantonio.play.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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicIndexLongClick(Bundle bundle) {
|
||||
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,14 @@
|
|||
package com.cappielloantonio.play.ui.fragment;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
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;
|
||||
|
|
@ -25,6 +27,7 @@ import com.cappielloantonio.play.ui.activity.MainActivity;
|
|||
import com.cappielloantonio.play.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.play.ui.adapter.ArtistAdapter;
|
||||
import com.cappielloantonio.play.ui.adapter.GenreAdapter;
|
||||
import com.cappielloantonio.play.ui.adapter.MusicFolderAdapter;
|
||||
import com.cappielloantonio.play.ui.adapter.PlaylistHorizontalAdapter;
|
||||
import com.cappielloantonio.play.ui.dialog.PlaylistEditorDialog;
|
||||
import com.cappielloantonio.play.util.Constants;
|
||||
|
|
@ -35,10 +38,13 @@ 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;
|
||||
|
|
@ -77,6 +83,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
initAppBar();
|
||||
initMusicFolderView();
|
||||
initAlbumView();
|
||||
initArtistView();
|
||||
initGenreView();
|
||||
|
|
@ -141,6 +148,28 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||
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);
|
||||
|
|
@ -273,4 +302,14 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
|||
dialog.setArguments(bundle);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicFolderClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMusicFolderLongClick(Bundle bundle) {
|
||||
Toast.makeText(requireContext(), "Long click!", Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ object Constants {
|
|||
const val PODCAST_OBJECT = "PODCAST_OBJECT"
|
||||
const val PODCAST_CHANNEL_OBJECT = "PODCAST_CHANNEL_OBJECT"
|
||||
const val INTERNET_RADIO_STATION_OBJECT = "INTERNET_RADIO_STATION_OBJECT"
|
||||
const val MUSIC_FOLDER_OBJECT = "MUSIC_FOLDER_OBJECT"
|
||||
const val MUSIC_DIRECTORY_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||
const val MUSIC_INDEX_OBJECT = "MUSIC_DIRECTORY_OBJECT"
|
||||
|
||||
const val ALBUM_RECENTLY_PLAYED = "ALBUM_RECENTLY_PLAYED"
|
||||
const val ALBUM_MOST_PLAYED = "ALBUM_MOST_PLAYED"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.cappielloantonio.play.util;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.play.subsonic.models.Artist;
|
||||
import com.cappielloantonio.play.subsonic.models.Index;
|
||||
import com.cappielloantonio.play.subsonic.models.Indexes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class IndexUtil {
|
||||
public static List<Artist> getArtist(Indexes indexes) {
|
||||
if (indexes.getIndices() == null) return Collections.emptyList();
|
||||
|
||||
ArrayList<Artist> toReturn = new ArrayList<>();
|
||||
|
||||
for (Index index : indexes.getIndices()) {
|
||||
if (index.getArtists() != null) {
|
||||
toReturn.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
|
||||
return toReturn;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.cappielloantonio.play.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.play.repository.DirectoryRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.Directory;
|
||||
|
||||
public class DirectoryViewModel extends AndroidViewModel {
|
||||
private final DirectoryRepository directoryRepository;
|
||||
|
||||
private MutableLiveData<String> id = new MutableLiveData<>(null);
|
||||
private MutableLiveData<String> name = new MutableLiveData<>(null);
|
||||
|
||||
private MutableLiveData<Directory> directory = new MutableLiveData<>(null);
|
||||
|
||||
public DirectoryViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
directoryRepository = new DirectoryRepository();
|
||||
}
|
||||
|
||||
public LiveData<Directory> getDirectory() {
|
||||
return directory;
|
||||
}
|
||||
|
||||
public void setMusicDirectoryId(String id) {
|
||||
this.id.setValue(id);
|
||||
}
|
||||
|
||||
public void setMusicDirectoryName(String name) {
|
||||
this.name.setValue(name);
|
||||
}
|
||||
|
||||
public void loadMusicDirectory(LifecycleOwner owner) {
|
||||
this.id.observe(owner, id -> directoryRepository.getMusicDirectory(id).observe(owner, directory -> this.directory.setValue(directory)));
|
||||
}
|
||||
|
||||
public void goBack() {
|
||||
this.id.setValue(this.directory.getValue().getParentId());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.cappielloantonio.play.viewmodel;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.play.repository.DirectoryRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.Indexes;
|
||||
import com.cappielloantonio.play.subsonic.models.MusicFolder;
|
||||
|
||||
public class IndexViewModel extends AndroidViewModel {
|
||||
private final DirectoryRepository directoryRepository;
|
||||
|
||||
private MusicFolder musicFolder;
|
||||
|
||||
private MutableLiveData<Indexes> indexes = new MutableLiveData<>(null);
|
||||
|
||||
public IndexViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
directoryRepository = new DirectoryRepository();
|
||||
}
|
||||
|
||||
public MutableLiveData<Indexes> getIndexes() {
|
||||
return directoryRepository.getIndexes(null, null);
|
||||
}
|
||||
|
||||
public String getMusicFolderName() {
|
||||
return musicFolder != null ? musicFolder.getName() : "";
|
||||
}
|
||||
|
||||
public void setMusicFolder(MusicFolder musicFolder) {
|
||||
this.musicFolder = musicFolder;
|
||||
}
|
||||
}
|
||||
|
|
@ -10,11 +10,14 @@ import androidx.lifecycle.MutableLiveData;
|
|||
|
||||
import com.cappielloantonio.play.repository.AlbumRepository;
|
||||
import com.cappielloantonio.play.repository.ArtistRepository;
|
||||
import com.cappielloantonio.play.repository.DirectoryRepository;
|
||||
import com.cappielloantonio.play.repository.GenreRepository;
|
||||
import com.cappielloantonio.play.repository.PlaylistRepository;
|
||||
import com.cappielloantonio.play.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.play.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.play.subsonic.models.Genre;
|
||||
import com.cappielloantonio.play.subsonic.models.Indexes;
|
||||
import com.cappielloantonio.play.subsonic.models.MusicFolder;
|
||||
import com.cappielloantonio.play.subsonic.models.Playlist;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -22,11 +25,14 @@ import java.util.List;
|
|||
public class LibraryViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "LibraryViewModel";
|
||||
|
||||
private final DirectoryRepository directoryRepository;
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final GenreRepository genreRepository;
|
||||
private final PlaylistRepository playlistRepository;
|
||||
|
||||
private final MutableLiveData<List<MusicFolder>> musicFolders = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Indexes> indexes = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<Playlist>> playlistSample = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<AlbumID3>> sampleAlbum = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<ArtistID3>> sampleArtist = new MutableLiveData<>(null);
|
||||
|
|
@ -35,12 +41,29 @@ public class LibraryViewModel extends AndroidViewModel {
|
|||
public LibraryViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
directoryRepository = new DirectoryRepository();
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
genreRepository = new GenreRepository();
|
||||
playlistRepository = new PlaylistRepository();
|
||||
}
|
||||
|
||||
public LiveData<List<MusicFolder>> getMusicFolders(LifecycleOwner owner) {
|
||||
if (musicFolders.getValue() == null) {
|
||||
directoryRepository.getMusicFolders().observe(owner, musicFolders::postValue);
|
||||
}
|
||||
|
||||
return musicFolders;
|
||||
}
|
||||
|
||||
public LiveData<Indexes> getIndexes(LifecycleOwner owner) {
|
||||
if (indexes.getValue() == null) {
|
||||
directoryRepository.getIndexes("0", null).observe(owner, indexes::postValue);
|
||||
}
|
||||
|
||||
return indexes;
|
||||
}
|
||||
|
||||
public LiveData<List<AlbumID3>> getAlbumSample(LifecycleOwner owner) {
|
||||
if (sampleAlbum.getValue() == null) {
|
||||
albumRepository.getAlbums("random", 10, null, null).observe(owner, sampleAlbum::postValue);
|
||||
|
|
|
|||
76
app/src/main/res/layout/fragment_directory.xml
Normal file
76
app/src/main/res/layout/fragment_directory.xml
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/directory_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="172dp"
|
||||
android:background="?attr/colorSurface"
|
||||
android:clipChildren="false"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/directory_title_label"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/directory_back_image_view"
|
||||
style="@style/Widget.Material3.Button.TonalButton.Icon"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:cornerRadius="30dp"
|
||||
app:icon="@drawable/ic_arrow_back"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/directory_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/global_padding_bottom"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
||||
|
||||
59
app/src/main/res/layout/fragment_index.xml
Normal file
59
app/src/main/res/layout/fragment_index.xml
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="8dp">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/index_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="172dp"
|
||||
android:background="?attr/colorSurface"
|
||||
android:clipChildren="false"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/index_title_label"
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="24dp"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/index_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:clipToPadding="false"
|
||||
android:paddingBottom="@dimen/global_padding_bottom"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
</LinearLayout>
|
||||
|
||||
|
|
@ -52,6 +52,41 @@
|
|||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<!-- Music Folder -->
|
||||
<LinearLayout
|
||||
android:id="@+id/library_music_folder_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="8dp">
|
||||
|
||||
<TextView
|
||||
style="@style/TitleLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/library_title_music_folder" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/music_folder_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:clipToPadding="false"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/library_music_folder_placeholder"
|
||||
layout="@layout/item_placeholder_album"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- Album -->
|
||||
<LinearLayout
|
||||
android:id="@+id/library_album_sector"
|
||||
|
|
@ -67,6 +102,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingTop="8dp"
|
||||
android:paddingEnd="8dp">
|
||||
|
||||
<TextView
|
||||
|
|
|
|||
56
app/src/main/res/layout/item_library_music_directory.xml
Normal file
56
app/src/main/res/layout/item_library_music_directory.xml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clipChildren="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_directory_cover_image_view"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="2dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_image_separator"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/music_directory_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_directory_title_text_view"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_directory_cover_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/music_directory_title_text_view"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_directory_more_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_directory_more_button"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/ic_more_vert"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_title_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_directory_title_text_view" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
52
app/src/main/res/layout/item_library_music_folder.xml
Normal file
52
app/src/main/res/layout/item_library_music_folder.xml
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clipChildren="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_folder_cover_image_view"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_image_separator"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/music_folder_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_folder_title_text_view"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_folder_cover_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_folder_cover_image_view" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/music_folder_title_text_view"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_folder_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_folder_more_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_folder_cover_image_view" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_folder_more_button"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:background="@drawable/ic_more_vert"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_folder_title_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_folder_title_text_view" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
56
app/src/main/res/layout/item_library_music_index.xml
Normal file
56
app/src/main/res/layout/item_library_music_index.xml
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clipChildren="false"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="3dp"
|
||||
android:paddingBottom="3dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_index_cover_image_view"
|
||||
android:layout_width="52dp"
|
||||
android:layout_height="52dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="2dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/cover_image_separator"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="52dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/music_index_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_index_title_text_view"
|
||||
app:layout_constraintStart_toEndOf="@+id/music_index_cover_image_view"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/music_index_title_text_view"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="marquee"
|
||||
android:paddingEnd="12dp"
|
||||
android:singleLine="true"
|
||||
android:text="@string/label_placeholder"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_index_cover_image_view"
|
||||
app:layout_constraintEnd_toStartOf="@+id/music_index_more_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/music_index_more_button"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:background="@drawable/ic_more_vert"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@id/music_index_title_text_view"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/music_index_title_text_view" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
@ -107,6 +107,9 @@
|
|||
<action
|
||||
android:id="@+id/action_libraryFragment_to_albumListPageFragment"
|
||||
app:destination="@id/albumListPageFragment" />
|
||||
<action
|
||||
android:id="@+id/action_libraryFragment_to_indexFragment"
|
||||
app:destination="@id/indexFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/downloadFragment"
|
||||
|
|
@ -279,6 +282,21 @@
|
|||
android:name="com.cappielloantonio.play.ui.fragment.PodcastChannelPageFragment"
|
||||
android:label="PodcastChannelPageFragment"
|
||||
tools:layout="@layout/fragment_podcast_channel_page" />
|
||||
<fragment
|
||||
android:id="@+id/directoryFragment"
|
||||
android:name="com.cappielloantonio.play.ui.fragment.DirectoryFragment"
|
||||
android:label="DirectoryFragment"
|
||||
tools:layout="@layout/fragment_directory" />
|
||||
<fragment
|
||||
android:id="@+id/indexFragment"
|
||||
android:name="com.cappielloantonio.play.ui.fragment.IndexFragment"
|
||||
android:label="IndexFragment"
|
||||
tools:layout="@layout/fragment_index">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_indexFragment_to_directoryFragment"
|
||||
app:destination="@id/directoryFragment" />
|
||||
</fragment>
|
||||
<dialog
|
||||
android:id="@+id/songBottomSheetDialog"
|
||||
android:name="com.cappielloantonio.play.ui.fragment.bottomsheetdialog.SongBottomSheetDialog"
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@
|
|||
<string name="home_title_podcast_channels_see_all_button">See all</string>
|
||||
<string name="label_dot_separator">•</string>
|
||||
<string name="label_placeholder">--</string>
|
||||
<string name="library_title_music_folder">Music folders</string>
|
||||
<string name="library_title_album">Albums</string>
|
||||
<string name="library_title_album_see_all_button">See all</string>
|
||||
<string name="library_title_artist">Artists</string>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue