mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 09:33:33 +00:00
feat: add play functionality to library folder/index items (#276)
This commit is contained in:
commit
cfd7cf314b
12 changed files with 220 additions and 26 deletions
15
USAGE.md
15
USAGE.md
|
|
@ -69,6 +69,21 @@ However, if you want to limit or change libraries you could use a workaround, if
|
||||||
|
|
||||||
You can create multiple users , one for each library, and save each of them in Tempus app.
|
You can create multiple users , one for each library, and save each of them in Tempus app.
|
||||||
|
|
||||||
|
### Folder or index playback
|
||||||
|
|
||||||
|
If your Subsonic-compatible server exposes the folder tree **or** provides an artist index (for example Gonic, Navidrome, or any backend with folder browsing enabled), Tempus lets you play an entire folder from anywhere in the library hierarchy:
|
||||||
|
|
||||||
|
<p align="left">
|
||||||
|
<img src="mockup/usage/music_folders_root.png" width=317 style="margin-right:16px;">
|
||||||
|
<img src="mockup/usage/music_folders_playback.png" width=317>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
- The **Library ▸ Music folders** screen shows each top-level folder with a play icon only after you drill into it. The root entry remains a simple navigator.
|
||||||
|
- When viewing **inner folders** **or artist index entries**, tap the new play button to immediately enqueue every audio track inside that folder/index and all nested subfolders.
|
||||||
|
- Video files are excluded automatically, so only playable audio ends up in the queue.
|
||||||
|
|
||||||
|
No extra config is needed—Tempus adjusts based on the connected backend.
|
||||||
|
|
||||||
### Now Playing Screen
|
### Now Playing Screen
|
||||||
|
|
||||||
On the main player control screen, tapping on the artwork will reveal a small collection of 4 buttons/icons.
|
On the main player control screen, tapping on the artwork will reveal a small collection of 4 buttons/icons.
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,11 @@ public interface ClickCallback {
|
||||||
default void onInternetRadioStationClick(Bundle bundle) {}
|
default void onInternetRadioStationClick(Bundle bundle) {}
|
||||||
default void onInternetRadioStationLongClick(Bundle bundle) {}
|
default void onInternetRadioStationLongClick(Bundle bundle) {}
|
||||||
default void onMusicFolderClick(Bundle bundle) {}
|
default void onMusicFolderClick(Bundle bundle) {}
|
||||||
|
default void onMusicFolderPlay(Bundle bundle) {}
|
||||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||||
|
default void onMusicDirectoryPlay(Bundle bundle) {}
|
||||||
default void onMusicIndexClick(Bundle bundle) {}
|
default void onMusicIndexClick(Bundle bundle) {}
|
||||||
|
default void onMusicIndexPlay(Bundle bundle) {}
|
||||||
default void onDownloadGroupLongClick(Bundle bundle) {}
|
default void onDownloadGroupLongClick(Bundle bundle) {}
|
||||||
default void onShareClick(Bundle bundle) {}
|
default void onShareClick(Bundle bundle) {}
|
||||||
default void onShareLongClick(Bundle bundle) {}
|
default void onShareLongClick(Bundle bundle) {}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||||
.into(holder.item.musicDirectoryCoverImageView);
|
.into(holder.item.musicDirectoryCoverImageView);
|
||||||
|
|
||||||
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||||
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
|
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -80,6 +80,7 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||||
itemView.setOnLongClickListener(v -> onLongClick());
|
itemView.setOnLongClickListener(v -> onLongClick());
|
||||||
|
|
||||||
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
|
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
|
||||||
|
item.musicDirectoryPlayButton.setOnClickListener(v -> onPlayClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
|
|
@ -107,5 +108,13 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onPlayClick() {
|
||||||
|
if (children.get(getBindingAdapterPosition()).isDir()) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, children.get(getBindingAdapterPosition()).getId());
|
||||||
|
click.onMusicDirectoryPlay(bundle);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||||
|
|
||||||
itemView.setOnClickListener(v -> onClick());
|
itemView.setOnClickListener(v -> onClick());
|
||||||
item.musicIndexMoreButton.setOnClickListener(v -> onClick());
|
item.musicIndexMoreButton.setOnClickListener(v -> onClick());
|
||||||
|
item.musicIndexPlayButton.setOnClickListener(v -> onPlayClick());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onClick() {
|
public void onClick() {
|
||||||
|
|
@ -83,5 +84,11 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
|
||||||
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||||
click.onMusicIndexClick(bundle);
|
click.onMusicIndexClick(bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onPlayClick() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(Constants.MUSIC_DIRECTORY_ID, artists.get(getBindingAdapterPosition()).getId());
|
||||||
|
click.onMusicIndexPlay(bundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,13 @@ import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||||
import com.cappielloantonio.tempo.model.Download;
|
import com.cappielloantonio.tempo.model.Download;
|
||||||
import com.cappielloantonio.tempo.service.MediaManager;
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
import com.cappielloantonio.tempo.service.MediaService;
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
|
||||||
import com.cappielloantonio.tempo.ui.dialog.DownloadDirectoryDialog;
|
import com.cappielloantonio.tempo.ui.dialog.DownloadDirectoryDialog;
|
||||||
|
|
@ -53,6 +59,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
private MusicDirectoryAdapter musicDirectoryAdapter;
|
private MusicDirectoryAdapter musicDirectoryAdapter;
|
||||||
|
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
private DirectoryRepository directoryRepository;
|
||||||
|
|
||||||
private MenuItem menuItem;
|
private MenuItem menuItem;
|
||||||
|
|
||||||
|
|
@ -77,6 +84,7 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentDirectoryBinding.inflate(inflater, container, false);
|
bind = FragmentDirectoryBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
directoryViewModel = new ViewModelProvider(requireActivity()).get(DirectoryViewModel.class);
|
||||||
|
directoryRepository = new DirectoryRepository();
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initDirectoryListView();
|
initDirectoryListView();
|
||||||
|
|
@ -197,4 +205,57 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
|
||||||
public void onMusicDirectoryClick(Bundle bundle) {
|
public void onMusicDirectoryClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMusicDirectoryPlay(Bundle bundle) {
|
||||||
|
String directoryId = bundle.getString(Constants.MUSIC_DIRECTORY_ID);
|
||||||
|
if (directoryId != null) {
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_collecting), Toast.LENGTH_SHORT).show();
|
||||||
|
collectAndPlayDirectorySongs(directoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectAndPlayDirectorySongs(String directoryId) {
|
||||||
|
List<Child> allSongs = new ArrayList<>();
|
||||||
|
AtomicInteger pendingRequests = new AtomicInteger(0);
|
||||||
|
|
||||||
|
collectSongsFromDirectory(directoryId, allSongs, pendingRequests, () -> {
|
||||||
|
if (!allSongs.isEmpty()) {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, allSongs, 0);
|
||||||
|
activity.setBottomSheetInPeek(true);
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_playing, allSongs.size()), Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_no_songs), Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectSongsFromDirectory(String directoryId, List<Child> allSongs, AtomicInteger pendingRequests, Runnable onComplete) {
|
||||||
|
pendingRequests.incrementAndGet();
|
||||||
|
|
||||||
|
directoryRepository.getMusicDirectory(directoryId).observe(getViewLifecycleOwner(), directory -> {
|
||||||
|
if (directory != null && directory.getChildren() != null) {
|
||||||
|
for (Child child : directory.getChildren()) {
|
||||||
|
if (child.isDir()) {
|
||||||
|
// It's a subdirectory, recurse into it
|
||||||
|
collectSongsFromDirectory(child.getId(), allSongs, pendingRequests, onComplete);
|
||||||
|
} else if (!child.isVideo()) {
|
||||||
|
// It's a song, add it to the list
|
||||||
|
synchronized (allSongs) {
|
||||||
|
allSongs.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement pending requests and check if we're done
|
||||||
|
if (pendingRequests.decrementAndGet() == 0) {
|
||||||
|
onComplete.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,27 +1,40 @@
|
||||||
package com.cappielloantonio.tempo.ui.fragment;
|
package com.cappielloantonio.tempo.ui.fragment;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.view.ViewCompat;
|
import androidx.core.view.ViewCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.session.MediaBrowser;
|
||||||
|
import androidx.media3.session.SessionToken;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
import com.cappielloantonio.tempo.databinding.FragmentIndexBinding;
|
import com.cappielloantonio.tempo.databinding.FragmentIndexBinding;
|
||||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||||
|
import com.cappielloantonio.tempo.repository.DirectoryRepository;
|
||||||
|
import com.cappielloantonio.tempo.service.MediaManager;
|
||||||
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||||
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
|
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||||
import com.cappielloantonio.tempo.ui.adapter.MusicIndexAdapter;
|
import com.cappielloantonio.tempo.ui.adapter.MusicIndexAdapter;
|
||||||
import com.cappielloantonio.tempo.util.Constants;
|
import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.IndexUtil;
|
import com.cappielloantonio.tempo.util.IndexUtil;
|
||||||
import com.cappielloantonio.tempo.viewmodel.IndexViewModel;
|
import com.cappielloantonio.tempo.viewmodel.IndexViewModel;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class IndexFragment extends Fragment implements ClickCallback {
|
public class IndexFragment extends Fragment implements ClickCallback {
|
||||||
|
|
@ -32,6 +45,8 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||||
private IndexViewModel indexViewModel;
|
private IndexViewModel indexViewModel;
|
||||||
|
|
||||||
private MusicIndexAdapter musicIndexAdapter;
|
private MusicIndexAdapter musicIndexAdapter;
|
||||||
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
private DirectoryRepository directoryRepository;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
@ -40,6 +55,7 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||||
bind = FragmentIndexBinding.inflate(inflater, container, false);
|
bind = FragmentIndexBinding.inflate(inflater, container, false);
|
||||||
View view = bind.getRoot();
|
View view = bind.getRoot();
|
||||||
indexViewModel = new ViewModelProvider(requireActivity()).get(IndexViewModel.class);
|
indexViewModel = new ViewModelProvider(requireActivity()).get(IndexViewModel.class);
|
||||||
|
directoryRepository = new DirectoryRepository();
|
||||||
|
|
||||||
initAppBar();
|
initAppBar();
|
||||||
initDirectoryListView();
|
initDirectoryListView();
|
||||||
|
|
@ -48,6 +64,18 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
initializeMediaBrowser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStop() {
|
||||||
|
releaseMediaBrowser();
|
||||||
|
super.onStop();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|
@ -107,4 +135,65 @@ public class IndexFragment extends Fragment implements ClickCallback {
|
||||||
public void onMusicIndexClick(Bundle bundle) {
|
public void onMusicIndexClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onMusicIndexPlay(Bundle bundle) {
|
||||||
|
String directoryId = bundle.getString(Constants.MUSIC_DIRECTORY_ID);
|
||||||
|
if (directoryId != null) {
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_collecting), Toast.LENGTH_SHORT).show();
|
||||||
|
collectAndPlayDirectorySongs(directoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeMediaBrowser() {
|
||||||
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseMediaBrowser() {
|
||||||
|
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectAndPlayDirectorySongs(String directoryId) {
|
||||||
|
List<Child> allSongs = new ArrayList<>();
|
||||||
|
AtomicInteger pendingRequests = new AtomicInteger(0);
|
||||||
|
|
||||||
|
collectSongsFromDirectory(directoryId, allSongs, pendingRequests, () -> {
|
||||||
|
if (!allSongs.isEmpty()) {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
MediaManager.startQueue(mediaBrowserListenableFuture, allSongs, 0);
|
||||||
|
activity.setBottomSheetInPeek(true);
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_playing, allSongs.size()), Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
Toast.makeText(requireContext(), getString(R.string.folder_play_no_songs), Toast.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void collectSongsFromDirectory(String directoryId, List<Child> allSongs, AtomicInteger pendingRequests, Runnable onComplete) {
|
||||||
|
pendingRequests.incrementAndGet();
|
||||||
|
|
||||||
|
directoryRepository.getMusicDirectory(directoryId).observe(getViewLifecycleOwner(), directory -> {
|
||||||
|
if (directory != null && directory.getChildren() != null) {
|
||||||
|
for (Child child : directory.getChildren()) {
|
||||||
|
if (child.isDir()) {
|
||||||
|
// It's a subdirectory, recurse into it
|
||||||
|
collectSongsFromDirectory(child.getId(), allSongs, pendingRequests, onComplete);
|
||||||
|
} else if (!child.isVideo()) {
|
||||||
|
// It's a song, add it to the list
|
||||||
|
synchronized (allSongs) {
|
||||||
|
allSongs.add(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrement pending requests and check if we're done
|
||||||
|
if (pendingRequests.decrementAndGet() == 0) {
|
||||||
|
onComplete.run();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -11,7 +11,11 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.session.MediaBrowser;
|
||||||
|
import androidx.media3.session.SessionToken;
|
||||||
import androidx.navigation.Navigation;
|
import androidx.navigation.Navigation;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
import androidx.recyclerview.widget.GridLayoutManager;
|
import androidx.recyclerview.widget.GridLayoutManager;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
|
|
@ -31,6 +35,8 @@ import com.cappielloantonio.tempo.util.Constants;
|
||||||
import com.cappielloantonio.tempo.util.Preferences;
|
import com.cappielloantonio.tempo.util.Preferences;
|
||||||
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
import com.cappielloantonio.tempo.viewmodel.LibraryViewModel;
|
||||||
import com.google.android.material.appbar.MaterialToolbar;
|
import com.google.android.material.appbar.MaterialToolbar;
|
||||||
|
import com.cappielloantonio.tempo.service.MediaService;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
@ -49,6 +55,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||||
|
|
||||||
private MaterialToolbar materialToolbar;
|
private MaterialToolbar materialToolbar;
|
||||||
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -79,6 +86,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
initializeMediaBrowser();
|
||||||
activity.setBottomNavigationBarVisibility(true);
|
activity.setBottomNavigationBarVisibility(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,4 +300,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
|
||||||
public void onMusicFolderClick(Bundle bundle) {
|
public void onMusicFolderClick(Bundle bundle) {
|
||||||
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
|
Navigation.findNavController(requireView()).navigate(R.id.indexFragment, bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeMediaBrowser() {
|
||||||
|
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,15 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<View
|
<ImageView
|
||||||
android:id="@+id/cover_image_separator"
|
android:id="@+id/music_directory_play_button"
|
||||||
android:layout_width="12dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="52dp"
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="@drawable/ic_play"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
|
android:visibility="invisible"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/music_directory_cover_image_view"
|
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_constraintStart_toEndOf="@+id/music_directory_cover_image_view"
|
||||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||||
|
|
||||||
|
|
@ -33,13 +36,14 @@
|
||||||
style="@style/LabelMedium"
|
style="@style/LabelMedium"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:text="@string/label_placeholder"
|
android:text="@string/label_placeholder"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_cover_image_view"
|
app:layout_constraintBottom_toBottomOf="@id/music_directory_cover_image_view"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/music_directory_more_button"
|
app:layout_constraintEnd_toStartOf="@+id/music_directory_more_button"
|
||||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
app:layout_constraintStart_toEndOf="@+id/music_directory_play_button"
|
||||||
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
app:layout_constraintTop_toTopOf="@+id/music_directory_cover_image_view" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
@ -54,17 +58,4 @@
|
||||||
app:layout_constraintBottom_toBottomOf="@id/music_directory_title_text_view"
|
app:layout_constraintBottom_toBottomOf="@id/music_directory_title_text_view"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="@+id/music_directory_title_text_view" />
|
app:layout_constraintTop_toTopOf="@+id/music_directory_title_text_view" />
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/music_directory_play_button"
|
|
||||||
android:layout_width="22dp"
|
|
||||||
android:layout_height="22dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:background="@drawable/ic_play"
|
|
||||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
|
||||||
android:visibility="invisible"
|
|
||||||
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>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
@ -20,12 +20,14 @@
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
<View
|
<ImageView
|
||||||
android:id="@+id/cover_image_separator"
|
android:id="@+id/music_index_play_button"
|
||||||
android:layout_width="12dp"
|
android:layout_width="32dp"
|
||||||
android:layout_height="52dp"
|
android:layout_height="32dp"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
|
android:background="@drawable/ic_play"
|
||||||
|
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/music_index_cover_image_view"
|
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_constraintStart_toEndOf="@+id/music_index_cover_image_view"
|
||||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||||
|
|
||||||
|
|
@ -34,13 +36,14 @@
|
||||||
style="@style/LabelMedium"
|
style="@style/LabelMedium"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="12dp"
|
||||||
android:ellipsize="marquee"
|
android:ellipsize="marquee"
|
||||||
android:paddingEnd="12dp"
|
android:paddingEnd="12dp"
|
||||||
android:singleLine="true"
|
android:singleLine="true"
|
||||||
android:text="@string/label_placeholder"
|
android:text="@string/label_placeholder"
|
||||||
app:layout_constraintBottom_toBottomOf="@id/music_index_cover_image_view"
|
app:layout_constraintBottom_toBottomOf="@id/music_index_cover_image_view"
|
||||||
app:layout_constraintEnd_toStartOf="@+id/music_index_more_button"
|
app:layout_constraintEnd_toStartOf="@+id/music_index_more_button"
|
||||||
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
|
app:layout_constraintStart_toEndOf="@+id/music_index_play_button"
|
||||||
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
app:layout_constraintTop_toTopOf="@+id/music_index_cover_image_view" />
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
||||||
|
|
@ -533,4 +533,8 @@
|
||||||
<string name="settings_album_detail_summary">If enabled, show the album details like genre, song count etc. on the album page</string>
|
<string name="settings_album_detail_summary">If enabled, show the album details like genre, song count etc. on the album page</string>
|
||||||
<string name="settings_artist_sort_by_album_count">Sort artists by album count</string>
|
<string name="settings_artist_sort_by_album_count">Sort artists by album count</string>
|
||||||
<string name="settings_artist_sort_by_album_count_summary">If enabled, sort the artists by album count. Sort by name if disabled.</string>
|
<string name="settings_artist_sort_by_album_count_summary">If enabled, sort the artists by album count. Sort by name if disabled.</string>
|
||||||
|
|
||||||
|
<string name="folder_play_collecting">Collecting songs from folder…</string>
|
||||||
|
<string name="folder_play_playing">Playing %d songs</string>
|
||||||
|
<string name="folder_play_no_songs">No songs found in folder</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
BIN
mockup/usage/music_folders_playback.png
Executable file
BIN
mockup/usage/music_folders_playback.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
mockup/usage/music_folders_root.png
Executable file
BIN
mockup/usage/music_folders_root.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 412 KiB |
Loading…
Add table
Add a link
Reference in a new issue