feat: add play functionality to library folder/index items

- add play button to inner folders in library
- implement recursive song collection from folders and subfolders
- filter out video files, play only audio tracks
- add user feedback with toast notifications
This commit is contained in:
Ante Budimir 2025-11-20 19:13:46 +02:00
parent c415db0cc5
commit be33401b6f
12 changed files with 220 additions and 26 deletions

View file

@ -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.

View file

@ -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) {}

View file

@ -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);
}
}
} }
} }

View file

@ -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);
}
} }
} }

View file

@ -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();
}
});
}
} }

View file

@ -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();
}
});
}
} }

View file

@ -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();
}
} }

View file

@ -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>

View file

@ -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

View file

@ -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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 412 KiB