Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
499644d041 | ||
|
|
21ed78d959 | ||
|
|
5ad99b9f27 | ||
|
|
3de5390140 | ||
|
|
d215581e19 | ||
|
|
54612c6b74 |
13
CHANGELOG.md
13
CHANGELOG.md
@@ -2,6 +2,19 @@
|
||||
|
||||
## Pending release
|
||||
|
||||
## What's Changed
|
||||
## [4.10.1](https://github.com/eddyizm/tempo/releases/tag/v4.10.1) (2026-02-08)
|
||||
* fix: Addressing some UI/UX quirks by @tiltshiftfocus in https://github.com/eddyizm/tempus/pull/413
|
||||
* fix: keep observer until data is received on continuousPlay bug by @eddyizm in https://github.com/eddyizm/tempus/pull/421
|
||||
* fix: album art now displays on android auto by @trobinson in https://github.com/eddyizm/tempus/pull/414
|
||||
* feat: improve landscape view and increase items per row on landscape view by @tvillega in https://github.com/eddyizm/tempus/pull/411
|
||||
|
||||
## New Contributors
|
||||
* @tiltshiftfocus made their first contribution in https://github.com/eddyizm/tempus/pull/413
|
||||
* @trobinson made their first contribution in https://github.com/eddyizm/tempus/pull/414
|
||||
|
||||
**Full Changelog**: https://github.com/eddyizm/tempus/compare/v4.9.8...v4.10.1
|
||||
|
||||
## What's Changed
|
||||
## [4.9.8](https://github.com/eddyizm/tempo/releases/tag/v4.9.8) (2026-02-02)
|
||||
* fix: missing Replay Gain metadata from .m4a files by @pgrit in https://github.com/eddyizm/tempus/pull/396
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
minSdkVersion 24
|
||||
targetSdk 35
|
||||
|
||||
versionCode 17
|
||||
versionName '4.9.8'
|
||||
versionCode 19
|
||||
versionName '4.10.1'
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
javaCompileOptions {
|
||||
|
||||
@@ -96,7 +96,12 @@
|
||||
android:resource="@xml/widget_info"/>
|
||||
</receiver>
|
||||
|
||||
|
||||
<provider
|
||||
android:name=".provider.AlbumArtContentProvider"
|
||||
android:authorities="com.cappielloantonio.tempo.provider"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
/>
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
@@ -13,6 +14,7 @@ import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
|
||||
@@ -197,7 +199,7 @@ class SessionMediaItem() {
|
||||
|
||||
fun getMediaItem(): MediaItem {
|
||||
val uri: Uri = getStreamUri()
|
||||
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
|
||||
val artworkUri = AlbumArtContentProvider.contentUri(coverArtId)
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", id)
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package com.cappielloantonio.tempo.provider;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class AlbumArtContentProvider extends ContentProvider {
|
||||
public static final String AUTHORITY = "com.cappielloantonio.tempo.provider";
|
||||
public static final String ALBUM_ART = "albumArt";
|
||||
private ExecutorService executor;
|
||||
|
||||
private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
|
||||
|
||||
static {
|
||||
uriMatcher.addURI(AUTHORITY, "albumArt/*", 1);
|
||||
}
|
||||
|
||||
public static Uri contentUri(String artworkId) {
|
||||
return new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY)
|
||||
.appendPath(ALBUM_ART)
|
||||
.appendPath(artworkId)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
|
||||
Context context = getContext();
|
||||
String albumId = uri.getLastPathSegment();
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(albumId, Preferences.getImageSize()));
|
||||
|
||||
try {
|
||||
// use pipe to communicate between background thread and caller of openFile()
|
||||
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
|
||||
ParcelFileDescriptor readSide = pipe[0];
|
||||
ParcelFileDescriptor writeSide = pipe[1];
|
||||
|
||||
// perform loading in background thread to avoid blocking UI
|
||||
executor.execute(() -> {
|
||||
try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(writeSide)) {
|
||||
|
||||
// request artwork from API using Glide
|
||||
File file = Glide.with(context)
|
||||
.asFile()
|
||||
.load(artworkUri)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.submit()
|
||||
.get();
|
||||
|
||||
// copy artwork down pipe returned by ContentProvider
|
||||
try (InputStream in = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int bytesRead;
|
||||
while ((bytesRead = in.read(buffer)) != -1) {
|
||||
out.write(buffer, 0, bytesRead);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
writeSide.closeWithError("Failed to load image: " + e.getMessage());
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
});
|
||||
|
||||
return readSide;
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new FileNotFoundException("Could not create pipe: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
executor = Executors.newFixedThreadPool(
|
||||
Math.max(2, Runtime.getRuntime().availableProcessors() / 2)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
if (executor != null) {
|
||||
executor.shutdown();
|
||||
try {
|
||||
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, @Nullable String[] strings, @Nullable String s, @Nullable String[] strings1, @Nullable String s1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, @Nullable ContentValues contentValues, @Nullable String s, @Nullable String[] strings) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.view.View;
|
||||
|
||||
@@ -22,6 +23,7 @@ import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
@@ -70,7 +72,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -217,7 +219,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -272,7 +274,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
@@ -397,7 +399,7 @@ public class AutomotiveRepository {
|
||||
List<Child> children = response.body().getSubsonicResponse().getIndexes().getChildren();
|
||||
|
||||
for (Child song : children) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(song.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(song.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(song.getTitle())
|
||||
@@ -451,7 +453,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (Child child : directory.getChildren()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(child.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(child.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(child.getTitle())
|
||||
@@ -550,7 +552,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (PodcastEpisode episode : episodes) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(episode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(episode.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(episode.getTitle())
|
||||
@@ -687,7 +689,7 @@ public class AutomotiveRepository {
|
||||
List<MediaItem> mediaItems = new ArrayList<>();
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
@@ -800,7 +802,7 @@ public class AutomotiveRepository {
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artist : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(artist.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(artist.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(artist.getName())
|
||||
@@ -822,7 +824,7 @@ public class AutomotiveRepository {
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||
for (AlbumID3 album : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(album.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(album.getCoverArtId());
|
||||
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder()
|
||||
.setTitle(album.getName())
|
||||
|
||||
@@ -33,6 +33,8 @@ import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
|
||||
private const val TAG = "BaseMediaService"
|
||||
|
||||
@UnstableApi
|
||||
open class BaseMediaService : MediaLibraryService() {
|
||||
companion object {
|
||||
@@ -82,7 +84,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
fun updateMediaItems(player: Player) {
|
||||
Log.d(javaClass.toString(), "update items")
|
||||
Log.d(TAG, "update items")
|
||||
val n = player.mediaItemCount
|
||||
val k = player.currentMediaItemIndex
|
||||
val current = player.currentPosition
|
||||
@@ -121,7 +123,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
fun initializePlayerListener(player: Player) {
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
Log.d(javaClass.toString(), "onMediaItemTransition" + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onMediaItemTransition" + player.currentMediaItemIndex)
|
||||
if (mediaItem == null) return
|
||||
|
||||
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
|
||||
@@ -131,7 +133,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onTracksChanged(tracks: Tracks) {
|
||||
Log.d(javaClass.toString(), "onTracksChanged " + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onTracksChanged " + player.currentMediaItemIndex)
|
||||
ReplayGainUtil.setReplayGain(player, tracks)
|
||||
val currentMediaItem = player.currentMediaItem
|
||||
if (currentMediaItem != null) {
|
||||
@@ -151,7 +153,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
if (player is ExoPlayer) {
|
||||
// https://stackoverflow.com/questions/56937283/exoplayer-shuffle-doesnt-reproduce-all-the-songs
|
||||
if (MediaManager.justStarted.get()) {
|
||||
Log.d(javaClass.toString(), "update shuffle order")
|
||||
Log.d(TAG, "update shuffle order")
|
||||
MediaManager.justStarted.set(false)
|
||||
val shuffledList = IntArray(player.mediaItemCount) { i -> i }
|
||||
shuffledList.shuffle()
|
||||
@@ -169,7 +171,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
Log.d(javaClass.toString(), "onIsPlayingChanged " + player.currentMediaItemIndex)
|
||||
Log.d(TAG, "onIsPlayingChanged " + player.currentMediaItemIndex)
|
||||
if (!isPlaying) {
|
||||
MediaManager.setPlayingPausedTimestamp(
|
||||
player.currentMediaItem,
|
||||
@@ -187,7 +189,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||
Log.d(javaClass.toString(), "onPlaybackStateChanged")
|
||||
Log.d(TAG, "onPlaybackStateChanged")
|
||||
super.onPlaybackStateChanged(playbackState)
|
||||
if (!player.hasNextMediaItem() &&
|
||||
playbackState == Player.STATE_ENDED &&
|
||||
@@ -204,7 +206,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
newPosition: Player.PositionInfo,
|
||||
reason: Int
|
||||
) {
|
||||
Log.d(javaClass.toString(), "onPositionDiscontinuity")
|
||||
Log.d(TAG, "onPositionDiscontinuity")
|
||||
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
|
||||
|
||||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
|
||||
@@ -228,7 +230,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
override fun onAudioSessionIdChanged(audioSessionId: Int) {
|
||||
Log.d(javaClass.toString(), "onAudioSessionIdChanged")
|
||||
Log.d(TAG, "onAudioSessionIdChanged")
|
||||
attachEqualizerIfPossible(audioSessionId)
|
||||
}
|
||||
})
|
||||
@@ -320,7 +322,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
}
|
||||
|
||||
private fun initializeMediaLibrarySession(player: Player) {
|
||||
Log.d(javaClass.toString(), "initializeMediaLibrarySession")
|
||||
Log.d(TAG, "initializeMediaLibrarySession")
|
||||
val sessionActivityPendingIntent =
|
||||
TaskStackBuilder.create(this).run {
|
||||
addNextIntent(Intent(baseContext, MainActivity::class.java))
|
||||
@@ -467,7 +469,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
Log.d(javaClass.toString(), "onCustomCommand")
|
||||
Log.d(TAG, "onCustomCommand")
|
||||
when (customCommand.customAction) {
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON -> session.player.shuffleModeEnabled = true
|
||||
CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF -> session.player.shuffleModeEnabled = false
|
||||
@@ -492,7 +494,7 @@ open class BaseMediaService : MediaLibraryService() {
|
||||
controller: ControllerInfo,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
Log.d(javaClass.toString(), "onAddMediaItems")
|
||||
Log.d(TAG, "onAddMediaItems")
|
||||
val updatedMediaItems = mediaItems.map { mediaItem ->
|
||||
val mediaMetadata = mediaItem.mediaMetadata
|
||||
val newMetadata = mediaMetadata.buildUpon()
|
||||
|
||||
@@ -444,24 +444,33 @@ public class MediaManager {
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void continuousPlay(MediaItem mediaItem, ListenableFuture<MediaBrowser> existingBrowserFuture) {
|
||||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getContinuousMix(mediaItem.mediaId, 25);
|
||||
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media != null && existingBrowserFuture != null) {
|
||||
Log.d(TAG, "Continuous play: adding " + media.size() + " tracks");
|
||||
enqueue(existingBrowserFuture, media, false);
|
||||
}
|
||||
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
public static void continuousPlay(MediaItem mediaItem,
|
||||
ListenableFuture<MediaBrowser> existingBrowserFuture) {
|
||||
if (mediaItem == null
|
||||
|| !Preferences.isContinuousPlayEnabled()
|
||||
|| !Preferences.isInstantMixUsable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix =
|
||||
getSongRepository().getContinuousMix(mediaItem.mediaId, 25);
|
||||
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media == null || media.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingBrowserFuture != null) {
|
||||
Log.d(TAG, "Continuous play: adding " + media.size() + " tracks");
|
||||
enqueue(existingBrowserFuture, media, true);
|
||||
}
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void saveChronology(MediaItem mediaItem) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.cappielloantonio.tempo.ui.activity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.content.IntentFilter;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
@@ -11,6 +13,7 @@ import android.os.Handler;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.splashscreen.SplashScreen;
|
||||
@@ -62,6 +65,7 @@ public class MainActivity extends BaseActivity {
|
||||
private BottomNavigationView bottomNavigationView;
|
||||
public NavController navController;
|
||||
private BottomSheetBehavior bottomSheetBehavior;
|
||||
private boolean isLandscape = false;
|
||||
private AssetLinkNavigator assetLinkNavigator;
|
||||
private AssetLinkUtil.AssetLink pendingAssetLink;
|
||||
|
||||
@@ -85,6 +89,8 @@ public class MainActivity extends BaseActivity {
|
||||
connectivityStatusBroadcastReceiver = new ConnectivityStatusBroadcastReceiver(this);
|
||||
connectivityStatusReceiverManager(true);
|
||||
|
||||
isLandscape = (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
|
||||
|
||||
init();
|
||||
checkConnectionType();
|
||||
getOpenSubsonicExtensions();
|
||||
@@ -141,6 +147,15 @@ public class MainActivity extends BaseActivity {
|
||||
} else {
|
||||
goToLogin();
|
||||
}
|
||||
|
||||
// Set bottom navigation height
|
||||
if (isLandscape) {
|
||||
ViewGroup.LayoutParams layoutParams = bottomNavigationView.getLayoutParams();
|
||||
Rect windowRect = new Rect();
|
||||
bottomNavigationView.getWindowVisibleDisplayFrame(windowRect);
|
||||
layoutParams.width = windowRect.height();
|
||||
bottomNavigationView.setLayoutParams(layoutParams);
|
||||
}
|
||||
}
|
||||
|
||||
// BOTTOM SHEET/NAVIGATION
|
||||
@@ -215,7 +230,9 @@ public class MainActivity extends BaseActivity {
|
||||
@Override
|
||||
public void onSlide(@NonNull View view, float slideOffset) {
|
||||
animateBottomSheet(slideOffset);
|
||||
animateBottomNavigation(slideOffset, navigationHeight);
|
||||
if (!isLandscape) {
|
||||
animateBottomNavigation(slideOffset, navigationHeight);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -173,10 +173,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
)
|
||||
)
|
||||
) {
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE);
|
||||
|
||||
if (songs.get(position).getDiscNumber() != null && !Objects.requireNonNull(songs.get(position).getDiscNumber()).toString().isBlank()) {
|
||||
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titleless, songs.get(position).getDiscNumber().toString()));
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
holder.item.differentDiskDividerSector.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if (album.getDiscTitles() != null) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class PlaybackSpeedDialog extends DialogFragment {
|
||||
private static final String TAG = "PlaybackSpeedDialog";
|
||||
|
||||
public interface PlaybackSpeedListener {
|
||||
void onSpeedSelected(float speed);
|
||||
}
|
||||
|
||||
private PlaybackSpeedListener listener;
|
||||
|
||||
private static final float[] SPEED_VALUES = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f};
|
||||
private static final String[] SPEED_LABELS = {"0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "1.75x", "2.0x"};
|
||||
|
||||
public void setPlaybackSpeedListener(PlaybackSpeedListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
int selectedIndex = getSelectedIndex(currentSpeed);
|
||||
|
||||
return new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setTitle(R.string.playback_speed_dialog_title)
|
||||
.setSingleChoiceItems(SPEED_LABELS, selectedIndex, (dialog, which) -> {
|
||||
float selectedSpeed = SPEED_VALUES[which];
|
||||
Preferences.setPlaybackSpeed(selectedSpeed);
|
||||
if (listener != null) {
|
||||
listener.onSpeedSelected(selectedSpeed);
|
||||
}
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.playback_speed_dialog_negative_button, (dialog, id) -> dialog.cancel())
|
||||
.create();
|
||||
}
|
||||
|
||||
private int getSelectedIndex(float currentSpeed) {
|
||||
for (int i = 0; i < SPEED_VALUES.length; i++) {
|
||||
if (Math.abs(SPEED_VALUES[i] - currentSpeed) < 0.01f) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return 2; // Default to 1.0x
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -49,6 +50,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private AlbumCatalogueViewModel albumCatalogueViewModel;
|
||||
|
||||
private AlbumCatalogueAdapter albumAdapter;
|
||||
private int spanCount = 2;
|
||||
private String currentSortOrder;
|
||||
private List<com.cappielloantonio.tempo.subsonic.models.AlbumID3> originalAlbums;
|
||||
|
||||
@@ -90,6 +92,10 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentAlbumCatalogueBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
initAppBar();
|
||||
initAlbumCatalogueView();
|
||||
initProgressLoader();
|
||||
@@ -133,8 +139,8 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initAlbumCatalogueView() {
|
||||
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.albumCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
albumAdapter = new AlbumCatalogueAdapter(this, true);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -50,6 +51,7 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private ArtistCatalogueViewModel artistCatalogueViewModel;
|
||||
|
||||
private ArtistCatalogueAdapter artistAdapter;
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -66,6 +68,10 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
bind = FragmentArtistCatalogueBinding.inflate(inflater, container, false);
|
||||
View view = bind.getRoot();
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
initAppBar();
|
||||
initArtistCatalogueView();
|
||||
|
||||
@@ -108,8 +114,8 @@ public class ArtistCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initArtistCatalogueView() {
|
||||
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.artistCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.artistCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.artistCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
artistAdapter = new ArtistCatalogueAdapter(this);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -63,6 +64,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -72,6 +75,10 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
artistPageViewModel = new ViewModelProvider(requireActivity()).get(ArtistPageViewModel.class);
|
||||
playbackViewModel = new ViewModelProvider(requireActivity()).get(PlaybackViewModel.class);
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
init(view);
|
||||
initAppBar();
|
||||
initArtistInfo();
|
||||
@@ -277,8 +284,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initAlbumsView() {
|
||||
bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.albumsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
albumCatalogueAdapter = new AlbumCatalogueAdapter(this, false);
|
||||
@@ -296,8 +303,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSimilarArtistsView() {
|
||||
bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
|
||||
bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 20, false));
|
||||
bind.similarArtistsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
artistCatalogueAdapter = new ArtistCatalogueAdapter(this);
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -32,6 +33,7 @@ import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.GenreCatalogueAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.GenreCatalogueViewModel;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
@@ -41,6 +43,7 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
private GenreCatalogueViewModel genreCatalogueViewModel;
|
||||
|
||||
private GenreCatalogueAdapter genreCatalogueAdapter;
|
||||
private int spanCount = 2;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
@@ -56,6 +59,10 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
View view = bind.getRoot();
|
||||
genreCatalogueViewModel = new ViewModelProvider(requireActivity()).get(GenreCatalogueViewModel.class);
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
spanCount = Preferences.getLandscapeItemsPerRow();
|
||||
}
|
||||
|
||||
init();
|
||||
initAppBar();
|
||||
initGenreCatalogueView();
|
||||
@@ -97,8 +104,8 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initGenreCatalogueView() {
|
||||
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
|
||||
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 16, false));
|
||||
bind.genreCatalogueRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), spanCount));
|
||||
bind.genreCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(spanCount, 16, false));
|
||||
bind.genreCatalogueRecyclerView.setHasFixedSize(true);
|
||||
|
||||
genreCatalogueAdapter = new GenreCatalogueAdapter(this);
|
||||
|
||||
@@ -39,6 +39,7 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
|
||||
import com.cappielloantonio.tempo.service.EqualizerManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaybackSpeedDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
|
||||
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
|
||||
@@ -522,13 +523,12 @@ public class PlayerControllerFragment extends Fragment {
|
||||
|
||||
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
|
||||
playbackSpeedButton.setOnClickListener(view -> {
|
||||
float currentSpeed = Preferences.getPlaybackSpeed();
|
||||
|
||||
currentSpeed += 0.25f;
|
||||
if (currentSpeed > 2.0f) currentSpeed = 0.5f;
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(currentSpeed));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, currentSpeed));
|
||||
Preferences.setPlaybackSpeed(currentSpeed);
|
||||
PlaybackSpeedDialog dialog = new PlaybackSpeedDialog();
|
||||
dialog.setPlaybackSpeedListener(speed -> {
|
||||
mediaBrowser.setPlaybackParameters(new PlaybackParameters(speed));
|
||||
playbackSpeedButton.setText(getString(R.string.player_playback_speed, speed));
|
||||
});
|
||||
dialog.show(requireActivity().getSupportFragmentManager(), null);
|
||||
});
|
||||
|
||||
skipSilenceToggleButton.setOnClickListener(view -> {
|
||||
|
||||
@@ -253,7 +253,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
|
||||
if (lines != null) {
|
||||
for (Line line : lines) {
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n");
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
|
||||
StringBuilder lyricsBuilder = new StringBuilder();
|
||||
for (Line line : lines) {
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n");
|
||||
lyricsBuilder.append(line.getValue().trim()).append("\n\n");
|
||||
}
|
||||
String lyrics = lyricsBuilder.toString();
|
||||
Spannable spannableString = new SpannableString(lyrics);
|
||||
@@ -328,7 +328,7 @@ public class PlayerLyricsFragment extends Fragment {
|
||||
boolean highlight = i == curIdx;
|
||||
if (highlight) highlightStart = offset;
|
||||
|
||||
int len = lines.get(i).getValue().length() + 1;
|
||||
int len = lines.get(i).getValue().length() + 2;
|
||||
final int lineStart = lines.get(i).getStart();
|
||||
spannableString.setSpan(new ClickableSpan() {
|
||||
@Override
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -15,6 +16,7 @@ import androidx.media3.common.HeartRating;
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.provider.AlbumArtContentProvider;
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
@@ -45,7 +47,7 @@ public class MappingUtil {
|
||||
Uri artworkUri = null;
|
||||
|
||||
if (coverArtId != null) {
|
||||
artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, Preferences.getImageSize()));
|
||||
artworkUri = AlbumArtContentProvider.contentUri(coverArtId);
|
||||
}
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
@@ -235,7 +237,7 @@ public class MappingUtil {
|
||||
|
||||
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
|
||||
Uri uri = getUri(podcastEpisode);
|
||||
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
|
||||
Uri artworkUri = AlbumArtContentProvider.contentUri(podcastEpisode.getCoverArtId());
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("id", podcastEpisode.getId());
|
||||
|
||||
@@ -29,6 +29,7 @@ object Preferences {
|
||||
private const val REPEAT_MODE = "repeat_mode"
|
||||
private const val IMAGE_CACHE_SIZE = "image_cache_size"
|
||||
private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
|
||||
private const val LANDSCAPE_ITEMS_PER_ROW = "landscape_items_per_row"
|
||||
private const val IMAGE_SIZE = "image_size"
|
||||
private const val MAX_BITRATE_WIFI = "max_bitrate_wifi"
|
||||
private const val MAX_BITRATE_MOBILE = "max_bitrate_mobile"
|
||||
@@ -304,6 +305,11 @@ object Preferences {
|
||||
return App.getInstance().preferences.getString(IMAGE_CACHE_SIZE, "500")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLandscapeItemsPerRow(): Int {
|
||||
return App.getInstance().preferences.getString(LANDSCAPE_ITEMS_PER_ROW, "4")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getImageSize(): Int {
|
||||
return App.getInstance().preferences.getString(IMAGE_SIZE, "-1")!!.toInt()
|
||||
|
||||
14
app/src/main/res/drawable/ic_graphic_eq_land.xml
Normal file
14
app/src/main/res/drawable/ic_graphic_eq_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M8,18c0.55,0 1,-0.45 1,-1L9,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM12,22c0.55,0 1,-0.45 1,-1L13,3c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v18c0,0.55 0.45,1 1,1zM4,14c0.55,0 1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v2c0,0.55 0.45,1 1,1zM16,18c0.55,0 1,-0.45 1,-1L17,7c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1v10c0,0.55 0.45,1 1,1zM19,11v2c0,0.55 0.45,1 1,1s1,-0.45 1,-1v-2c0,-0.55 -0.45,-1 -1,-1s-1,0.45 -1,1z" />
|
||||
</group>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_home_land.xml
Normal file
14
app/src/main/res/drawable/ic_home_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M12,5.69l5,4.5V18h-2v-6H9v6H7v-7.81l5,-4.5M12,3L2,12h3v8h6v-6h2v6h6v-8h3L12,3z" />
|
||||
</group>
|
||||
</vector>
|
||||
14
app/src/main/res/drawable/ic_play_for_work_land.xml
Normal file
14
app/src/main/res/drawable/ic_play_for_work_land.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<group
|
||||
android:pivotY="12"
|
||||
android:pivotX="12"
|
||||
android:rotation="270">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M11,5v5.59L7.5,10.59l4.5,4.5 4.5,-4.5L13,10.59L13,5h-2zM6,14c0,3.31 2.69,6 6,6s6,-2.69 6,-6h-2c0,2.21 -1.79,4 -4,4s-4,-1.79 -4,-4L6,14z"/>
|
||||
</group>
|
||||
</vector>
|
||||
68
app/src/main/res/layout-land/activity_main.xml
Normal file
68
app/src/main/res/layout-land/activity_main.xml
Normal file
@@ -0,0 +1,68 @@
|
||||
<?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:background="?attr/colorSurface"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="75dp"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.bottomnavigation.BottomNavigationView
|
||||
android:id="@+id/bottom_navigation"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="75dp"
|
||||
android:rotation="90"
|
||||
android:layout_gravity="center"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:visibility="gone"
|
||||
app:menu="@menu/bottom_nav_menu" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/player_bottom_sheet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:behavior_hideable="true"
|
||||
app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
|
||||
app:layout_behavior="@string/bottom_sheet_behavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/offline_mode_text_view"
|
||||
style="@style/NoConnectionTextView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/activity_info_offline_mode"
|
||||
android:textSize="6sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
305
app/src/main/res/layout-land/fragment_album_page.xml
Normal file
305
app/src/main/res/layout-land/fragment_album_page.xml
Normal file
@@ -0,0 +1,305 @@
|
||||
<?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"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/anim_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1">
|
||||
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/album_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
android:paddingStart="20dp"
|
||||
app:layout_scrollFlags="exitUntilCollapsed">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/album_cover_image_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginLeft="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginRight="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_name_label"
|
||||
style="@style/LabelExtraLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="8dp"
|
||||
android:singleLine="false"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="@+id/album_cover_image_view"
|
||||
app:layout_constraintStart_toStartOf="@+id/album_cover_image_view"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_cover_image_view" />
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/album_other_info_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/album_name_label"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/album_name_label"
|
||||
app:layout_constraintTop_toTopOf="@+id/album_name_label">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:background="@drawable/ic_arrow_down" />
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_artist_label"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_name_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_release_year_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_artist_label" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/album_detail_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_release_year_label"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_genres_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_song_count_duration_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingVertical="2dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_genres_textview" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_notes_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:justificationMode="inter_word"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_song_count_duration_textview" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_release_years_textview"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:paddingVertical="4dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_notes_textview" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingEnd="12dp"
|
||||
android:paddingBottom="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/album_page_button_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_play_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/album_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/album_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<ToggleButton
|
||||
android:id="@+id/button_favorite"
|
||||
android:layout_width="34dp"
|
||||
android:layout_height="34dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="0dp"
|
||||
android:background="@drawable/button_favorite_selector"
|
||||
android:checked="false"
|
||||
android:foreground="?android:attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:text=""
|
||||
android:textOff=""
|
||||
android:textOn="" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_bio_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/album_page_button_layout"
|
||||
tools:ignore="NotSibling" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/album_bio_label" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingTop="0dp"
|
||||
android:paddingBottom="75dp"
|
||||
android:clipToPadding="false"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
218
app/src/main/res/layout-land/fragment_playlist_page.xml
Normal file
218
app/src/main/res/layout-land/fragment_playlist_page.xml
Normal file
@@ -0,0 +1,218 @@
|
||||
<?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/anim_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_collapseMode="pin"
|
||||
app:navigationIcon="@drawable/ic_arrow_back" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingBottom="@dimen/global_padding_bottom"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/app_bar_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/playlist_info_sector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorSurface"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_top_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_top_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_top_right"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_top_left"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_bottom_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toStartOf="@id/playlist_cover_image_view_bottom_right"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/playlist_cover_image_view_top_left" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/playlist_cover_image_view_bottom_right"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="64dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintDimensionRatio="H,1:1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/playlist_cover_image_view_bottom_left"
|
||||
app:layout_constraintTop_toTopOf="@id/playlist_cover_image_view_bottom_left" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_name_label"
|
||||
style="@style/LabelExtraLarge"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="8dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_cover_image_view_bottom_left" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_song_count_label"
|
||||
style="@style/LabelMedium"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_name_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/playlist_duration_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_song_count_label" />
|
||||
|
||||
<View
|
||||
android:id="@+id/upper_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_duration_label" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playlist_page_button_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingTop="4dp"
|
||||
android:paddingBottom="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/upper_button_divider">
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_page_play_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/playlist_page_play_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_play"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/playlist_page_shuffle_button"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_weight="1"
|
||||
android:padding="10dp"
|
||||
android:text="@string/playlist_page_shuffle_button"
|
||||
android:textAllCaps="false"
|
||||
app:icon="@drawable/ic_shuffle"
|
||||
app:iconGravity="textStart"
|
||||
app:iconPadding="18dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/album_bio_label"
|
||||
style="@style/LabelSmall"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAlignment="center"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||
|
||||
<View
|
||||
android:id="@+id/bottom_button_divider"
|
||||
style="@style/Divider"
|
||||
android:layout_marginStart="18dp"
|
||||
android:layout_marginEnd="18dp"
|
||||
android:layout_marginBottom="18dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/playlist_page_button_layout" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/song_recycler_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipToPadding="false"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:paddingTop="8dp"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</LinearLayout>
|
||||
@@ -46,6 +46,8 @@
|
||||
style="@style/BodyLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:lineSpacingExtra="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
15
app/src/main/res/menu-land/bottom_nav_menu.xml
Normal file
15
app/src/main/res/menu-land/bottom_nav_menu.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/homeFragment"
|
||||
android:icon="@drawable/ic_home_land"
|
||||
android:title="@string/menu_home_label" />
|
||||
<item
|
||||
android:id="@+id/libraryFragment"
|
||||
android:icon="@drawable/ic_graphic_eq_land"
|
||||
android:title="@string/menu_library_label" />
|
||||
<item
|
||||
android:id="@+id/downloadFragment"
|
||||
android:icon="@drawable/ic_play_for_work_land"
|
||||
android:title="@string/menu_download_label" />
|
||||
</menu>
|
||||
@@ -264,4 +264,18 @@
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
<string-array name="landscape_items_per_row">
|
||||
<item>3"</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
</string-array>
|
||||
<string-array name="landscape_items_per_row_values">
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
<item>5</item>
|
||||
<item>6</item>
|
||||
<item>7</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
@@ -217,6 +217,8 @@
|
||||
<string name="menu_unpin_button">Remove from home screen</string>
|
||||
<string name="menu_sort_year">Year</string>
|
||||
<string name="player_playback_speed">%1$.2fx</string>
|
||||
<string name="playback_speed_dialog_title">Playback Speed</string>
|
||||
<string name="playback_speed_dialog_negative_button">Cancel</string>
|
||||
<string name="player_queue_clean_all_button">Clean play queue</string>
|
||||
<string name="player_queue_save_queue_success">Saved play queue</string>
|
||||
<string name="player_queue_save_to_playlist">Save Queue to Playlist</string>
|
||||
@@ -403,6 +405,7 @@
|
||||
<string name="settings_summary_transcoding">Priority given to the transcoding mode. If set to \"Direct play\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_download">Download transcoded media. If enabled, the download endpoint will not be used, but the following settings. \n\n If \"Transcode format for downloads\" is set to \"Direct download\" the bitrate of the file will not be changed.</string>
|
||||
<string name="settings_summary_transcoding_estimate_content_length">When the file is transcoded on the fly, the client usually does not show the track length. It is possible to request the servers that support the functionality to estimate the duration of the track being played, but the response times may take longer.</string>
|
||||
<string name="settings_summary_landscape_items_per_row">Applies to all album and artist listings. Defaults to 4</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_summary">If enabled, starred artists will be downloaded for offline use.</string>
|
||||
<string name="settings_sync_starred_artists_for_offline_use_title">Sync starred artists for offline use</string>
|
||||
<string name="settings_sync_starred_albums_for_offline_use_summary">If enabled, starred albums will be downloaded for offline use.</string>
|
||||
@@ -423,6 +426,8 @@
|
||||
<string name="settings_title_transcoding">Transcoding</string>
|
||||
<string name="settings_title_transcoding_download">Transcoding Download</string>
|
||||
<string name="settings_title_ui">UI</string>
|
||||
<string name="settings_title_ui_landscape_items_per_row">Items per row on landscape</string>
|
||||
<string name="settings_title_ui_landscape_items_per_row_dialog">Number of items per row</string>
|
||||
<string name="settings_transcoded_download">Transcoded download</string>
|
||||
<string name="settings_version_summary" translatable="false">3.1.0</string>
|
||||
<string name="settings_version_title">Version</string>
|
||||
|
||||
@@ -137,6 +137,15 @@
|
||||
android:summary="@string/search_sort_summary"
|
||||
android:key="sort_search_chronologically" />
|
||||
|
||||
<ListPreference
|
||||
app:defaultValue="4"
|
||||
app:dialogTitle="@string/settings_title_ui_landscape_items_per_row_dialog"
|
||||
app:entries="@array/landscape_items_per_row"
|
||||
app:entryValues="@array/landscape_items_per_row_values"
|
||||
app:key="landscape_items_per_row"
|
||||
android:summary="@string/settings_summary_landscape_items_per_row"
|
||||
app:title="@string/settings_title_ui_landscape_items_per_row" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory app:title="@string/settings_title_playlist">
|
||||
|
||||
4
fastlane/metadata/android/en-US/changelogs/18.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/18.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fix: Addressing some UI/UX quirks
|
||||
fix: keep observer until data is received on continuousPlay bug
|
||||
fix: album art now displays on android auto
|
||||
feat: improve landscape view and increase items per row on landscape view
|
||||
4
fastlane/metadata/android/en-US/changelogs/19.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/19.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
fix: Addressing some UI/UX quirks
|
||||
fix: keep observer until data is received on continuousPlay bug
|
||||
fix: album art now displays on android auto
|
||||
feat: improve landscape view and increase items per row on landscape view
|
||||
Reference in New Issue
Block a user