66 Commits
3.6.0 ... 3.8.0

Author SHA1 Message Date
CappielloAntonio
499929ad55 gradle: downgrade buildToolsVersion to fix GitHub workflow build error 2024-03-24 19:59:41 +01:00
CappielloAntonio
7b6d2c62a5 style: code cleanup 2024-03-24 19:45:56 +01:00
CappielloAntonio
ff6bf20c30 style: refined the design of favorite and rating indicators 2024-03-24 18:50:05 +01:00
CappielloAntonio
58d540b939 feat: as an option show the item's rating and whether it is marked as a favorite 2024-03-24 00:45:19 +01:00
CappielloAntonio
4b9eaa8c3d style: code cleanup 2024-03-23 22:49:05 +01:00
CappielloAntonio
03700d9e4c fix: removed placeholders causing stuttering during interface loading 2024-03-23 22:41:44 +01:00
CappielloAntonio
374dbb58bb style: code cleanup 2024-03-23 22:40:37 +01:00
CappielloAntonio
a88658ac8f chore: removed unused placeholders 2024-03-23 22:39:51 +01:00
CappielloAntonio
0e97eab744 feat: implemented customizable home, allowing users to toggle visibility of elements and change their order 2024-03-23 21:33:11 +01:00
CappielloAntonio
309eca0764 gradle: dependencies update 2024-03-23 15:51:06 +01:00
CappielloAntonio
fd85f36411 Merge remote-tracking branch 'origin/main' 2024-03-16 17:44:28 +01:00
CappielloAntonio
b4180afa36 refactor: refactored artist page layout for consistent vertical scrolling 2024-03-16 17:44:18 +01:00
CappielloAntonio
2712b73dac feat: added sorting by newest album added 2024-03-16 16:28:33 +01:00
CappielloAntonio
302458e76b feat: added additional information about the album on the dedicated detail page 2024-03-16 15:50:06 +01:00
CappielloAntonio
dd085a2cdb gradle: Media3 dependencies update 2024-03-16 12:57:57 +01:00
CappielloAntonio
7a58ad5494 Merge pull request #187 from chengyuhui/fix-negative-gain
fix: fix negative replay gain values
2024-03-16 12:52:59 +01:00
CappielloAntonio
84234849a4 Merge pull request #188 from chengyuhui/fix-load-control-fdroid
fix: fix load control for F-Droid builds
2024-03-16 12:48:06 +01:00
CappielloAntonio
6f6f596432 gradle: gradle update 2024-03-16 12:39:36 +01:00
Midori Kochiya
4ff2ed38c7 Fix load control for F-Droid builds 2024-03-11 15:13:13 +08:00
Midori Kochiya
321994496a Fix negative replay gain values 2024-03-10 19:16:03 +08:00
CappielloAntonio
3e1c3133ca fix: fix crash with TypeToken and reflection 2024-02-19 21:07:47 +01:00
CappielloAntonio
10b9d7ec76 fix: fix "Now Playing" scrobble implementation 2024-02-18 19:35:49 +01:00
CappielloAntonio
1980e53a27 fix: fix scrolling issue in server registration dialog 2024-02-18 19:15:14 +01:00
CappielloAntonio
54e988b70e feat: added "Recent songs" and "Random" menu items in Android Auto 2024-02-18 19:06:58 +01:00
CappielloAntonio
14d6128df0 feat: added optional information about audio quality for horizontal track adapters 2024-02-18 17:21:52 +01:00
CappielloAntonio
7488346804 feat: added optional information about audio quality for horizontal track adapters 2024-02-18 17:14:41 +01:00
CappielloAntonio
733102a8a4 feat: implemented karaoke mode for synchronized lyrics 2024-02-18 16:29:42 +01:00
CappielloAntonio
28fef53590 fix: disable shuffle button if there isn't a top song list 2024-02-17 23:54:50 +01:00
CappielloAntonio
e35aed9cc4 feat: implemented synchronized lyrics display 2024-02-17 23:44:49 +01:00
CappielloAntonio
111a17350b chore: code cleanup 2024-02-17 23:43:17 +01:00
CappielloAntonio
54be869081 feat: implemented new API getLyricsBySongId for retrieving (synced) song lyrics based on song ID 2024-02-17 23:43:02 +01:00
CappielloAntonio
b9462d7374 feat: check and save usable OpenSubsonic APIs 2024-02-17 23:39:25 +01:00
CappielloAntonio
234c9a10d2 Merge pull request #171 from victoralvesf/portuguese-support
feat: add brazilian portuguese localization
2024-02-17 11:37:09 +01:00
CappielloAntonio
817c3b02e5 gradle: dependencies update 2024-02-17 11:34:43 +01:00
Victor Alves
1f65b4c321 feat: add brazilian portuguese localization 2024-02-15 02:16:02 -03:00
CappielloAntonio
ab0e1ead45 Update README.md 2024-02-04 19:00:17 +01:00
antonio
73b368d202 style: code cleanup 2024-01-29 16:42:09 +01:00
antonio
f293a0116b Merge remote-tracking branch 'origin/main' 2024-01-29 12:29:22 +01:00
CappielloAntonio
ff5bec30c0 Update github_release.yml 2024-01-29 12:29:08 +01:00
antonio
5c66bed477 gradle: code version update 2024-01-29 12:18:34 +01:00
antonio
d2068106e4 gradle: standardized code and name version for every flavors, temporarily downgrade com.google.android.material 2024-01-29 11:45:13 +01:00
antonio
b7b02854d5 style: improved readability by modifying settings summary 2024-01-29 10:35:25 +01:00
antonio
0edafc2d8e gradle: updated gradle 2024-01-29 10:33:20 +01:00
antonio
279302737d fix: sort genres alphabetically as server's default sorting could lead to unpredictable results 2024-01-28 23:40:03 +01:00
antonio
634de67d74 feat: added an option to prevent phone from going into sleep mode if in-app 2024-01-28 23:22:03 +01:00
antonio
cd44368d66 feat: save user's layout choice and always use user preference 2024-01-28 23:03:20 +01:00
antonio
ae8aa56602 style: code cleanup 2024-01-28 19:36:25 +01:00
CappielloAntonio
34d6692dae Merge pull request #122 from DelightLane/duration_crash
fix: null checking for song without duration info
2024-01-28 19:35:56 +01:00
antonio
8d2f0edbab fix: variable removed by accident 2024-01-28 19:19:47 +01:00
antonio
d4d7aaba2b style: code cleanup 2024-01-28 19:15:42 +01:00
CappielloAntonio
d690df86d8 Merge pull request #150 from GallowsDove/album-catalogue-fixes
fix: fix loading and filtering issues with AlbumCatalogue
2024-01-28 19:08:35 +01:00
antonio
e8f3cdbb48 style: code cleanup 2024-01-28 18:30:50 +01:00
CappielloAntonio
33aa38e885 Merge pull request #145 from GallowsDove/new-download-fix
fix: fix new download caching
2024-01-28 18:24:43 +01:00
antonio
2faba71df0 feat: Implemented shuffle feature for downloaded songs based on the set filter 2024-01-28 18:02:48 +01:00
antonio
1d3a32be5d fix: refined scrobbling logic for the NowPlaying feature across all flavors 2024-01-28 15:46:36 +01:00
antonio
5b8e7d1404 style: code cleanup 2024-01-28 15:45:02 +01:00
antonio
85a5d01e72 style: code cleanup 2024-01-28 15:44:55 +01:00
CappielloAntonio
c7b17f2214 Merge pull request #155 from caiocotts/main
feat: send "now playing" scrobbles to server
2024-01-28 15:19:10 +01:00
caiocotts
d8c8a783de Send "now playing" scrobbles to server. 2024-01-22 21:41:54 -05:00
CappielloAntonio
293b0f71c8 Merge pull request #137 from dnno/update-german-localization
feat: Update German localization
2024-01-21 18:54:05 +01:00
antonio
e6e0e399e0 gradle: dependencies update 2024-01-21 18:51:57 +01:00
GallowsDove
3223de5d03 fix: Remove duplicated line in AlbumCatalogueViewModel 2024-01-18 00:06:37 +01:00
GallowsDove
c6d08d6a3f fix: Fix issues with AlbumCatalogue 2024-01-17 19:21:16 +01:00
GallowsDove
375501f282 fix: Fix new download caching 2024-01-16 19:58:45 +01:00
Reinhard Prechtl
0a62f0d81e Upgrade german locale 2024-01-11 11:58:52 +01:00
DelightLane
12f09b2201 fix: null checking for song without duration info 2023-12-21 11:43:22 +09:00
119 changed files with 2893 additions and 1470 deletions

View File

@@ -28,6 +28,13 @@ jobs:
- name: Make gradlew executable
run: chmod +x ./gradlew
- name: Setup build tool version variable
shell: bash
run: |
BUILD_TOOL_VERSION=$(ls /usr/local/lib/android/sdk/build-tools/ | tail -n 1)
echo "BUILD_TOOL_VERSION=$BUILD_TOOL_VERSION" >> $GITHUB_ENV
echo Last build tool version is: $BUILD_TOOL_VERSION
- name: Build APK
id: build
run: bash ./gradlew assembleTempoRelease
@@ -41,6 +48,8 @@ jobs:
alias: ${{ secrets.KEY_ALIAS_GITHUB }}
keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
env:
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
- name: Make artifact
uses: actions/upload-artifact@v2
@@ -48,34 +57,6 @@ jobs:
name: app-release-signed
path: ${{steps.sign_apk.outputs.signedReleaseFile}}
# - name: Build AAB
# run: bash ./gradlew bundleRelease
# - name: Sign AAB
# id: sign_aab
# uses: r0adkll/sign-android-release@v1
# with:
# releaseDirectory: app/build/outputs/bundle/release
# signingKeyBase64: ${{ secrets.KEYSTORE_BASE64 }}
# alias: ${{ secrets.KEY_ALIAS_GITHUB }}
# keyStorePassword: ${{ secrets.KEYSTORE_PASSWORD }}
# keyPassword: ${{ secrets.KEY_PASSWORD_GITHUB }}
# - name: Make artifact
# uses: actions/upload-artifact@v2
# with:
# name: app-release-signed
# path: ${{steps.sign_aab.outputs.signedReleaseFile}}
# - name: Build Changelog
# id: changelog
# uses: ardalanamini/auto-changelog@v3
# with:
# mention-authors: false
# mention-new-contributors: false
# include-compare: false
# semver: false
- name: Create Release
id: create_release
uses: actions/create-release@v1
@@ -83,7 +64,6 @@ jobs:
tag_name: ${{ github.ref }}
release_name: Release v${{ github.ref }}
body: '> Changelog coming soon'
# body: ${{ steps.changelog.outputs.changelog }}
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -96,13 +76,3 @@ jobs:
asset_path: ${{steps.sign_apk.outputs.signedReleaseFile}}
asset_name: app-tempo-release.apk
asset_content_type: application/zip
# - name: Upload AAB
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ github.token }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: ${{steps.sign_aab.outputs.signedReleaseFile}}
# asset_name: app-release.aab
# asset_content_type: application/zip

5
.idea/gradle.xml generated
View File

@@ -4,16 +4,15 @@
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="17" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>

2
.idea/misc.xml generated
View File

@@ -191,7 +191,7 @@
</map>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="17" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">

View File

@@ -6,6 +6,12 @@
<b>Access your music library on all your android devices</b>
</p>
<p align="center">
<a href="https://github.com/CappielloAntonio/tempo/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.cappielloantonio.tempo"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
</p>
**Tempo** is an open-source and lightweight music client for Subsonic, designed and built natively for Android. It provides a seamless and intuitive music streaming experience, allowing you to access and play your Subsonic music library directly from your Android device.
Tempo does not rely on magic algorithms to decide what you should listen to. Instead, the interface is built around your listening history, randomness, and optionally integrates with services like Last.fm to personalize your music experience.

View File

@@ -4,12 +4,15 @@ apply plugin: 'kotlin-parcelize'
android {
compileSdk = 34
buildToolsVersion = "34.0.0"
buildToolsVersion = '34.0.0'
defaultConfig {
minSdkVersion 24
targetSdkVersion 34
versionCode 24
versionName '3.7.0'
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
javaCompileOptions {
@@ -28,15 +31,11 @@ android {
tempo {
dimension = "default"
applicationId 'com.cappielloantonio.tempo'
versionCode 23
versionName '3.6.0'
}
notquitemy {
dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
versionCode 1
versionName "1.0.0"
}
}
@@ -66,38 +65,36 @@ android {
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// AndroidX
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.6'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.1'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.appcompat:appcompat:1.6.1"
// Android Material
implementation 'com.google.android.material:material:1.11.0'
implementation 'com.google.android.material:material:1.10.0'
// Glide
implementation 'com.github.bumptech.glide:glide:4.16.0'
implementation 'com.github.bumptech.glide:annotations:4.16.0'
// Media3
implementation 'androidx.media3:media3-session:1.2.0'
implementation 'androidx.media3:media3-common:1.2.0'
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation 'androidx.media3:media3-ui:1.2.0'
tempoImplementation 'androidx.media3:media3-cast:1.2.0'
implementation 'androidx.media3:media3-session:1.3.0'
implementation 'androidx.media3:media3-common:1.3.0'
implementation 'androidx.media3:media3-exoplayer:1.3.0'
implementation 'androidx.media3:media3-ui:1.3.0'
tempoImplementation 'androidx.media3:media3-cast:1.3.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.6.1'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.10.0'
}

View File

@@ -22,4 +22,7 @@
-keepattributes SourceFile, LineNumberTable
-keep public class * extends java.lang.Exception
-keep class retrofit2.** { *; }
-keep class retrofit2.** { *; }
-keep class **.reflect.TypeToken { *; }
-keep class * extends **.reflect.TypeToken

View File

@@ -12,6 +12,9 @@ import java.util.List;
@Dao
public interface ChronologyDao {
@Query("SELECT * FROM chronology WHERE server == :server GROUP BY id ORDER BY timestamp DESC LIMIT :count")
LiveData<List<Chronology>> getLastPlayed(String server, int count);
@Query("SELECT * FROM chronology WHERE timestamp >= :startDate AND timestamp < :endDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 9")
LiveData<List<Chronology>> getAllFrom(long startDate, long endDate, String server);

View File

@@ -0,0 +1,11 @@
package com.cappielloantonio.tempo.model
import androidx.annotation.Keep
@Keep
data class HomeSector(
val id: String,
val sectorTitle: String,
var isVisible: Boolean,
val order: Int,
)

View File

@@ -8,6 +8,7 @@ import com.cappielloantonio.tempo.interfaces.DecadesCallback;
import com.cappielloantonio.tempo.interfaces.MediaCallback;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
import com.cappielloantonio.tempo.subsonic.models.Child;
import java.util.ArrayList;
@@ -131,9 +132,10 @@ public class AlbumRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
albums.sort(Comparator.comparing(AlbumID3::getYear));
Collections.reverse(albums);
artistsAlbum.setValue(albums);
}
}
@@ -170,6 +172,29 @@ public class AlbumRepository {
return album;
}
public MutableLiveData<AlbumInfo> getAlbumInfo(String id) {
MutableLiveData<AlbumInfo> albumInfo = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getAlbumInfo2(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumInfo() != null) {
albumInfo.setValue(response.body().getSubsonicResponse().getAlbumInfo());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return albumInfo;
}
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
App.getSubsonicClientInstance(false)
.getBrowsingClient()
@@ -250,7 +275,7 @@ public class AlbumRepository {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
if (!response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty() && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else {
callback.onLoadYear(-1);

View File

@@ -2,9 +2,13 @@ package com.cappielloantonio.tempo.repository;
import android.net.Uri;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Observer;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.UnstableApi;
@@ -12,9 +16,13 @@ import androidx.media3.session.LibraryResult;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.database.AppDatabase;
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
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.service.DownloaderManager;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.Artist;
@@ -26,6 +34,7 @@ import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
@@ -44,6 +53,7 @@ import retrofit2.Response;
public class AutomotiveRepository {
private final SessionMediaItemDao sessionMediaItemDao = AppDatabase.getInstance().sessionMediaItemDao();
private final ChronologyDao chronologyDao = AppDatabase.getInstance().chronologyDao();
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getAlbums(String prefix, String type, int size) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
@@ -132,6 +142,66 @@ public class AutomotiveRepository {
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getRandomSongs(int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
App.getSubsonicClientInstance(false)
.getAlbumSongListClient()
.getRandomSongs(100, null, null)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
List<Child> songs = response.body().getSubsonicResponse().getRandomSongs().getSongs();
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
listenableFuture.setException(t);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getRecentlyPlayedSongs(String server, int count) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();
chronologyDao.getLastPlayed(server, count).observeForever(new Observer<List<Chronology>>() {
@Override
public void onChanged(List<Chronology> chronology) {
if (chronology != null && !chronology.isEmpty()) {
List<Child> songs = new ArrayList<>(chronology);
setChildrenMetadata(songs);
List<MediaItem> mediaItems = MappingUtil.mapMediaItems(songs);
LibraryResult<ImmutableList<MediaItem>> libraryResult = LibraryResult.ofItemList(ImmutableList.copyOf(mediaItems), null);
listenableFuture.set(libraryResult);
} else {
listenableFuture.set(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE));
}
chronologyDao.getLastPlayed(server, count).removeObserver(this);
}
});
return listenableFuture;
}
public ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> getStarredAlbums(String prefix) {
final SettableFuture<LibraryResult<ImmutableList<MediaItem>>> listenableFuture = SettableFuture.create();

View File

@@ -8,7 +8,9 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.Genre;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import retrofit2.Call;
import retrofit2.Callback;
@@ -39,7 +41,7 @@ public class GenreRepository {
if (size != -1) {
genres.setValue(genreList.subList(0, Math.min(size, genreList.size())));
} else {
genres.setValue(genreList);
genres.setValue(genreList.stream().sorted(Comparator.comparing(Genre::getGenre)).collect(Collectors.toList()));
}
}
}

View File

@@ -0,0 +1,37 @@
package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class OpenRepository {
public MutableLiveData<LyricsList> getLyricsBySongId(String id) {
MutableLiveData<LyricsList> lyricsList = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getOpenClient()
.getLyricsBySongId(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyricsList() != null) {
lyricsList.setValue(response.body().getSubsonicResponse().getLyricsList());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return lyricsList;
}
}

View File

@@ -104,10 +104,10 @@ public class SongRepository {
return randomSongsSample;
}
public void scrobble(String id) {
public void scrobble(String id, boolean submission) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.scrobble(id)
.scrobble(id, submission)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {

View File

@@ -6,7 +6,11 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.interfaces.SystemCallback;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
import com.cappielloantonio.tempo.subsonic.models.ResponseStatus;
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
@@ -43,8 +47,8 @@ public class SystemRepository {
});
}
public MutableLiveData<Boolean> ping() {
MutableLiveData<Boolean> pingResult = new MutableLiveData<>();
public MutableLiveData<SubsonicResponse> ping() {
MutableLiveData<SubsonicResponse> pingResult = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSystemClient()
@@ -53,16 +57,39 @@ public class SystemRepository {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
pingResult.postValue(true);
pingResult.postValue(response.body().getSubsonicResponse());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
pingResult.postValue(false);
pingResult.postValue(null);
}
});
return pingResult;
}
public MutableLiveData<List<OpenSubsonicExtension>> getOpenSubsonicExtensions() {
MutableLiveData<List<OpenSubsonicExtension>> extensionsResult = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSystemClient()
.getOpenSubsonicExtensions()
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null) {
extensionsResult.postValue(response.body().getSubsonicResponse().getOpenSubsonicExtensions());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
extensionsResult.postValue(null);
}
});
return extensionsResult;
}
}

View File

@@ -31,9 +31,10 @@ public class DownloaderManager {
private final Context context;
private final DataSource.Factory dataSourceFactory;
private final HashMap<String, Download> downloads;
private final DownloadIndex downloadIndex;
private static HashMap<String, Download> downloads;
public DownloaderManager(Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
this.context = context.getApplicationContext();
this.dataSourceFactory = dataSourceFactory;
@@ -61,19 +62,11 @@ public class DownloaderManager {
}
public boolean isDownloaded(MediaItem mediaItem) {
@Nullable Download download = downloads.get(mediaItem.mediaId);
return download != null && download.state != Download.STATE_FAILED;
return isDownloaded(mediaItem.mediaId);
}
public boolean areDownloaded(List<MediaItem> mediaItems) {
for (MediaItem mediaItem : mediaItems) {
@Nullable Download download = downloads.get(mediaItem.mediaId);
if (download != null && download.state != Download.STATE_FAILED) {
return true;
}
}
return false;
return mediaItems.stream().anyMatch(this::isDownloaded);
}
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
@@ -92,6 +85,7 @@ public class DownloaderManager {
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
deleteDatabase(download.getId());
downloads.remove(download.getId());
}
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
@@ -122,23 +116,33 @@ public class DownloaderManager {
return download != null ? download.getTitle() : null;
}
public static void updateRequestDownload(Download download) {
updateDatabase(download.request.id);
downloads.put(download.request.id, download);
}
public static void removeRequestDownload(Download download) {
deleteDatabase(download.request.id);
downloads.remove(download.request.id);
}
private static DownloadRepository getDownloadRepository() {
return new DownloadRepository();
}
public static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
private static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
getDownloadRepository().insert(download);
}
public static void deleteDatabase(String id) {
private static void deleteDatabase(String id) {
getDownloadRepository().delete(id);
}
public static void deleteAllDatabase() {
private static void deleteAllDatabase() {
getDownloadRepository().deleteAll();
}
public static void updateDatabase(String id) {
private static void updateDatabase(String id) {
getDownloadRepository().update(id);
}
}
}

View File

@@ -95,7 +95,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
DownloaderManager.updateDatabase(download.request.id);
DownloaderManager.updateRequestDownload(download);
} else if (download.state == Download.STATE_FAILED) {
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
@@ -109,7 +109,7 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
@Override
public void onDownloadRemoved(@NonNull DownloadManager downloadManager, Download download) {
DownloaderManager.deleteDatabase(download.request.id);
DownloaderManager.removeRequestDownload(download);
}
}
}

View File

@@ -293,9 +293,9 @@ public class MediaManager {
getQueueRepository().setPlayingPausedTimestamp(mediaItem.mediaId, ms);
}
public static void scrobble(MediaItem mediaItem) {
public static void scrobble(MediaItem mediaItem, boolean submission) {
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"), submission);
}
}

View File

@@ -7,6 +7,7 @@ import com.cappielloantonio.tempo.subsonic.api.internetradio.InternetRadioClient
import com.cappielloantonio.tempo.subsonic.api.mediaannotation.MediaAnnotationClient;
import com.cappielloantonio.tempo.subsonic.api.medialibraryscanning.MediaLibraryScanningClient;
import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClient;
import com.cappielloantonio.tempo.subsonic.api.open.OpenClient;
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
@@ -35,6 +36,7 @@ public class Subsonic {
private BookmarksClient bookmarksClient;
private InternetRadioClient internetRadioClient;
private SharingClient sharingClient;
private OpenClient openClient;
public Subsonic(SubsonicPreferences preferences) {
this.preferences = preferences;
@@ -128,6 +130,13 @@ public class Subsonic {
return sharingClient;
}
public OpenClient getOpenClient() {
if (openClient == null) {
openClient = new OpenClient(this);
}
return openClient;
}
public String getUrl() {
String url = preferences.getServerUrl() + "/rest/";
return url.replace("//rest", "/rest");

View File

@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaAnnotationClient {
private static final String TAG = "BrowsingClient";
private static final String TAG = "MediaAnnotationClient";
private final Subsonic subsonic;
private final MediaAnnotationService mediaAnnotationService;
@@ -34,8 +34,8 @@ public class MediaAnnotationClient {
return mediaAnnotationService.setRating(subsonic.getParams(), id, rating);
}
public Call<ApiResponse> scrobble(String id) {
public Call<ApiResponse> scrobble(String id, boolean submission) {
Log.d(TAG, "scrobble()");
return mediaAnnotationService.scrobble(subsonic.getParams(), id);
return mediaAnnotationService.scrobble(subsonic.getParams(), id, submission);
}
}

View File

@@ -20,5 +20,5 @@ public interface MediaAnnotationService {
Call<ApiResponse> setRating(@QueryMap Map<String, String> params, @Query("id") String id, @Query("rating") int rating);
@GET("scrobble")
Call<ApiResponse> scrobble(@QueryMap Map<String, String> params, @Query("id") String id);
Call<ApiResponse> scrobble(@QueryMap Map<String, String> params, @Query("id") String id, @Query("submission") Boolean submission);
}

View File

@@ -9,7 +9,7 @@ import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class MediaRetrievalClient {
private static final String TAG = "BrowsingClient";
private static final String TAG = "MediaRetrievalClient";
private final Subsonic subsonic;
private final MediaRetrievalService mediaRetrievalService;

View File

@@ -0,0 +1,26 @@
package com.cappielloantonio.tempo.subsonic.api.open;
import android.util.Log;
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
import com.cappielloantonio.tempo.subsonic.Subsonic;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import retrofit2.Call;
public class OpenClient {
private static final String TAG = "OpenClient";
private final Subsonic subsonic;
private final OpenService openService;
public OpenClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.openService = new RetrofitClient(subsonic).getRetrofit().create(OpenService.class);
}
public Call<ApiResponse> getLyricsBySongId(String id) {
Log.d(TAG, "getLyricsBySongId()");
return openService.getLyricsBySongId(subsonic.getParams(), id);
}
}

View File

@@ -0,0 +1,15 @@
package com.cappielloantonio.tempo.subsonic.api.open;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
public interface OpenService {
@GET("getLyricsBySongId")
Call<ApiResponse> getLyricsBySongId(@QueryMap Map<String, String> params, @Query("id") String id);
}

View File

@@ -28,4 +28,9 @@ public class SystemClient {
Log.d(TAG, "getLicense()");
return systemService.getLicense(subsonic.getParams());
}
public Call<ApiResponse> getOpenSubsonicExtensions() {
Log.d(TAG, "getOpenSubsonicExtensions()");
return systemService.getOpenSubsonicExtensions(subsonic.getParams());
}
}

View File

@@ -14,4 +14,7 @@ public interface SystemService {
@GET("getLicense")
Call<ApiResponse> getLicense(@QueryMap Map<String, String> params);
@GET("getOpenSubsonicExtensions")
Call<ApiResponse> getOpenSubsonicExtensions(@QueryMap Map<String, String> params);
}

View File

@@ -7,5 +7,5 @@ import com.google.gson.annotations.SerializedName
@Keep
class ApiResponse {
@SerializedName("subsonic-response")
lateinit var subsonicResponse: SubsonicResponse;
lateinit var subsonicResponse: SubsonicResponse
}

View File

@@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep
@Keep
class Line {
var start: Int? = null
lateinit var value: String
}

View File

@@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep
@Keep
class LyricsList {
var structuredLyrics: List<StructuredLyrics>? = null
}

View File

@@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep
@Keep
class OpenSubsonicExtension {
var name: String? = null
var versions: List<Int>? = null
}

View File

@@ -0,0 +1,13 @@
package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep
@Keep
class StructuredLyrics {
var displayArtist: String? = null
var displayTitle: String? = null
var lang: String? = null
var offset: Int = 0
var synced: Boolean = false
var line: List<Line>? = null
}

View File

@@ -51,4 +51,7 @@ class SubsonicResponse {
var version: String? = null
var type: String? = null
var serverVersion: String? = null
var openSubsonic: Boolean? = null
var openSubsonicExtensions: List<OpenSubsonicExtension>? = null
var lyricsList: LyricsList? = null
}

View File

@@ -70,6 +70,7 @@ public class MainActivity extends BaseActivity {
init();
checkConnectionType();
getOpenSubsonicExtensions();
}
@Override
@@ -334,11 +335,25 @@ public class MainActivity extends BaseActivity {
private void pingServer() {
if (Preferences.getToken() != null) {
mainViewModel.ping().observe(this, isPingSuccessfull -> {
if (!isPingSuccessfull && Preferences.showServerUnreachableDialog()) {
mainViewModel.ping().observe(this, subsonicResponse -> {
if (subsonicResponse == null && Preferences.showServerUnreachableDialog()) {
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
dialog.show(getSupportFragmentManager(), null);
}
if (subsonicResponse != null) {
Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
}
});
}
}
private void getOpenSubsonicExtensions() {
if (Preferences.getToken() != null) {
mainViewModel.getOpenSubsonicExtensions().observe(this, openSubsonicExtensions -> {
if (openSubsonicExtensions != null) {
Preferences.setOpenSubsonicExtensions(openSubsonicExtensions);
}
});
}
}

View File

@@ -6,6 +6,7 @@ import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.view.WindowManager;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
@@ -37,6 +38,7 @@ public class BaseActivity extends AppCompatActivity {
initializeDownloader();
checkBatteryOptimization();
checkPermission();
checkAlwaysOnDisplay();
}
@Override
@@ -66,6 +68,12 @@ public class BaseActivity extends AppCompatActivity {
}
}
private void checkAlwaysOnDisplay() {
if (Preferences.isDisplayAlwaysOn()) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
private boolean detectBatteryOptimization() {
String packageName = getPackageName();
PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);

View File

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
@@ -23,6 +24,9 @@ import java.util.List;
public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAdapter.ViewHolder> implements Filterable {
private final ClickCallback click;
private String currentFilter;
private boolean showArtist;
private final Filter filtering = new Filter() {
@Override
protected FilterResults performFiltering(CharSequence constraint) {
@@ -32,6 +36,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
filteredList.addAll(albumsFull);
} else {
String filterPattern = constraint.toString().toLowerCase().trim();
currentFilter = filterPattern;
for (AlbumID3 item : albumsFull) {
if (item.getName().toLowerCase().contains(filterPattern)) {
@@ -48,8 +53,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
albums.clear();
albums.addAll((List) results.values);
albums = (List<AlbumID3>) results.values;
notifyDataSetChanged();
}
};
@@ -57,9 +61,12 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
private List<AlbumID3> albums;
private List<AlbumID3> albumsFull;
public AlbumCatalogueAdapter(ClickCallback click) {
public AlbumCatalogueAdapter(ClickCallback click, boolean showArtist) {
this.click = click;
this.albums = Collections.emptyList();
this.albumsFull = Collections.emptyList();
this.currentFilter = "";
this.showArtist = showArtist;
}
@NonNull
@@ -75,6 +82,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
holder.item.artistNameLabel.setVisibility(showArtist ? View.VISIBLE : View.GONE);
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
@@ -92,9 +100,8 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
}
public void setItems(List<AlbumID3> albums) {
this.albums = albums;
this.albumsFull = new ArrayList<>(albums);
notifyDataSetChanged();
filtering.filter(currentFilter);
}
@Override
@@ -158,8 +165,12 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
case Constants.ALBUM_ORDER_BY_RANDOM:
Collections.shuffle(albums);
break;
case Constants.ALBUM_ORDER_BY_RECENTLY_ADDED:
albums.sort(Comparator.comparing(AlbumID3::getCreated));
Collections.reverse(albums);
break;
}
notifyDataSetChanged();
}
}
}

View File

@@ -33,6 +33,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
private String filterValue;
private List<Child> songs;
private List<Child> shuffling;
private List<Child> grouped;
public DownloadHorizontalAdapter(ClickCallback click) {
@@ -82,6 +83,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
this.songs = songs;
this.grouped = groupSong(songs);
this.shuffling = shufflingSong(new ArrayList<>(songs));
notifyDataSetChanged();
}
@@ -90,6 +92,10 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
return grouped.get(id);
}
public List<Child> getShuffling() {
return shuffling;
}
@Override
public int getItemViewType(int position) {
return position;
@@ -136,6 +142,27 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
return songs;
}
private List<Child> shufflingSong(List<Child> songs) {
if (filterValue == null) {
return songs;
}
switch (filterKey) {
case Constants.DOWNLOAD_TYPE_TRACK:
return songs.stream().filter(child -> child.getId().equals(filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_ALBUM:
return songs.stream().filter(child -> Objects.equals(child.getAlbumId(), filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_GENRE:
return songs.stream().filter(child -> Objects.equals(child.getGenre(), filterValue)).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_YEAR:
return songs.stream().filter(child -> Objects.equals(child.getYear(), Integer.valueOf(filterValue))).collect(Collectors.toList());
case Constants.DOWNLOAD_TYPE_ARTIST:
return songs.stream().filter(child -> Objects.equals(child.getArtistId(), filterValue)).collect(Collectors.toList());
default:
return songs;
}
}
private String countSong(String filterKey, String filterValue, List<Child> songs) {
if (filterValue != null) {
switch (filterKey) {
@@ -159,7 +186,15 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
Child song = grouped.get(position);
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
holder.item.downloadedItemSubtitleTextView.setText(
holder.itemView.getContext().getString(
R.string.song_subtitle_formatter,
MusicUtil.getReadableString(song.getArtist()),
MusicUtil.getReadableDurationString(song.getDuration(), false),
""
)
);
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder

View File

@@ -0,0 +1,76 @@
package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemHorizontalHomeSectorBinding;
import com.cappielloantonio.tempo.databinding.ItemHorizontalPlaylistDialogTrackBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.HomeSector;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import java.util.Collections;
import java.util.List;
public class HomeSectorHorizontalAdapter extends RecyclerView.Adapter<HomeSectorHorizontalAdapter.ViewHolder> {
private List<HomeSector> sectors;
public HomeSectorHorizontalAdapter() {
this.sectors = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalHomeSectorBinding view = ItemHorizontalHomeSectorBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
HomeSector sector = sectors.get(position);
holder.item.homeSectorTitleCheckBox.setText(sector.getSectorTitle());
holder.item.homeSectorTitleCheckBox.setChecked(sector.isVisible());
}
@Override
public int getItemCount() {
return sectors.size();
}
public List<HomeSector> getItems() {
return this.sectors;
}
public void setItems(List<HomeSector> sectors) {
this.sectors = sectors;
notifyDataSetChanged();
}
public HomeSector getItem(int id) {
return sectors.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalHomeSectorBinding item;
ViewHolder(ItemHorizontalHomeSectorBinding item) {
super(item.getRoot());
this.item = item;
this.item.homeSectorTitleCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> onCheck(isChecked));
}
private void onCheck(boolean isChecked) {
sectors.get(getBindingAdapterPosition()).setVisible(isChecked);
}
}
}

View File

@@ -2,9 +2,11 @@ package com.cappielloantonio.tempo.ui.adapter;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.media3.session.MediaBrowser;
import androidx.recyclerview.widget.RecyclerView;
@@ -17,6 +19,7 @@ import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
@@ -46,7 +49,14 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
Child song = songs.get(position);
holder.item.queueSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.queueSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
holder.item.queueSongSubtitleTextView.setText(
holder.itemView.getContext().getString(
R.string.song_subtitle_formatter,
MusicUtil.getReadableString(song.getArtist()),
MusicUtil.getReadableDurationString(song.getDuration(), false),
MusicUtil.getReadableAudioQualityString(song)
)
);
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
@@ -59,12 +69,33 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
if (position < index) {
holder.item.queueSongTitleTextView.setAlpha(0.2f);
holder.item.queueSongSubtitleTextView.setAlpha(0.2f);
holder.item.ratingIndicatorImageView.setAlpha(0.2f);
} else {
holder.item.queueSongTitleTextView.setAlpha(1.0f);
holder.item.queueSongSubtitleTextView.setAlpha(1.0f);
holder.item.ratingIndicatorImageView.setAlpha(1.0f);
}
}
});
if (Preferences.showItemRating()) {
if (song.getStarred() == null && song.getUserRating() == null) {
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
}
holder.item.preferredIcon.setVisibility(song.getStarred() != null ? View.VISIBLE : View.GONE);
holder.item.ratingBarLayout.setVisibility(song.getUserRating() != null ? View.VISIBLE : View.GONE);
if (song.getUserRating() != null) {
holder.item.oneStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 1 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.twoStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 2 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.threeStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 3 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.fourStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 4 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.fiveStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 5 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
}
} else {
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
}
}
public List<Child> getItems() {

View File

@@ -6,6 +6,7 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
@@ -17,6 +18,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList;
import java.util.Collections;
@@ -49,13 +51,26 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
Child song = songs.get(position);
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
holder.item.searchResultSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(this.showAlbum ? song.getAlbum() : song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration() != null ? song.getDuration() : 0, false)));
holder.item.searchResultSongSubtitleTextView.setText(
holder.itemView.getContext().getString(
R.string.song_subtitle_formatter,
MusicUtil.getReadableString(
this.showAlbum ?
song.getAlbum() :
song.getArtist()
),
MusicUtil.getReadableDurationString(song.getDuration(), false),
MusicUtil.getReadableAudioQualityString(song)
)
);
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.VISIBLE);
holder.item.searchResultDownloadIndicatorImageView.setVisibility(View.VISIBLE);
} else {
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
holder.item.searchResultDownloadIndicatorImageView.setVisibility(View.GONE);
}
if (showCoverArt) CustomGlideRequest.Builder
@@ -69,6 +84,25 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
if (!showCoverArt && (position > 0 && songs.get(position - 1) != null && songs.get(position - 1).getDiscNumber() != null && songs.get(position).getDiscNumber() != null && songs.get(position - 1).getDiscNumber() < songs.get(position).getDiscNumber())) {
holder.item.differentDiskDivider.setVisibility(View.VISIBLE);
}
if (Preferences.showItemRating()) {
if (song.getStarred() == null && song.getUserRating() == null) {
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
}
holder.item.preferredIcon.setVisibility(song.getStarred() != null ? View.VISIBLE : View.GONE);
holder.item.ratingBarLayout.setVisibility(song.getUserRating() != null ? View.VISIBLE : View.GONE);
if (song.getUserRating() != null) {
holder.item.oneStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 1 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.twoStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 2 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.threeStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 3 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.fourStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 4 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
holder.item.fiveStarIcon.setImageDrawable(AppCompatResources.getDrawable(holder.itemView.getContext(), song.getUserRating() >= 5 ? R.drawable.ic_star : R.drawable.ic_star_outlined));
}
} else {
holder.item.ratingIndicatorImageView.setVisibility(View.GONE);
}
}
@Override

View File

@@ -0,0 +1,115 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogHomeRearrangementBinding;
import com.cappielloantonio.tempo.ui.adapter.HomeSectorHorizontalAdapter;
import com.cappielloantonio.tempo.viewmodel.HomeRearrangementViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Collections;
import java.util.Objects;
public class HomeRearrangementDialog extends DialogFragment {
private DialogHomeRearrangementBinding bind;
private HomeRearrangementViewModel homeRearrangementViewModel;
private HomeSectorHorizontalAdapter homeSectorHorizontalAdapter;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogHomeRearrangementBinding.inflate(getLayoutInflater());
homeRearrangementViewModel = new ViewModelProvider(requireActivity()).get(HomeRearrangementViewModel.class);
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.home_rearrangement_dialog_title)
.setPositiveButton(R.string.home_rearrangement_dialog_positive_button, (dialog, id) -> { })
.setNeutralButton(R.string.home_rearrangement_dialog_neutral_button, (dialog, id) -> { })
.setNegativeButton(R.string.home_rearrangement_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
public void onStart() {
super.onStart();
setButtonAction();
initSectorView();
}
@Override
public void onDestroyView() {
super.onDestroyView();
homeRearrangementViewModel.closeDialog();
bind = null;
}
private void setButtonAction() {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
homeRearrangementViewModel.saveHomeSectorList(homeSectorHorizontalAdapter.getItems());
dismiss();
});
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
homeRearrangementViewModel.resetHomeSectorList();
dismiss();
});
}
private void initSectorView() {
bind.homeSectorItemRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.homeSectorItemRecyclerView.setHasFixedSize(true);
homeSectorHorizontalAdapter = new HomeSectorHorizontalAdapter();
bind.homeSectorItemRecyclerView.setAdapter(homeSectorHorizontalAdapter);
homeSectorHorizontalAdapter.setItems(homeRearrangementViewModel.getHomeSectorList());
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0) {
int originalPosition = -1;
int fromPosition = -1;
int toPosition = -1;
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
if (originalPosition == -1) originalPosition = viewHolder.getBindingAdapterPosition();
fromPosition = viewHolder.getBindingAdapterPosition();
toPosition = target.getBindingAdapterPosition();
Collections.swap(homeSectorHorizontalAdapter.getItems(), fromPosition, toPosition);
Objects.requireNonNull(recyclerView.getAdapter()).notifyItemMoved(fromPosition, toPosition);
return false;
}
@Override
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
homeRearrangementViewModel.orderSectorLiveListAfterSwap(homeSectorHorizontalAdapter.getItems());
originalPosition = -1;
fromPosition = -1;
toPosition = -1;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
}
}
).attachToRecyclerView(bind.homeSectorItemRecyclerView);
}
}

View File

@@ -83,7 +83,7 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
playlistChooserViewModel.getPlaylistList(requireActivity()).observe(requireActivity(), playlists -> {
if (playlists != null) {
if (playlists.size() > 0) {
if (!playlists.isEmpty()) {
if (bind != null) bind.noPlaylistsCreatedTextView.setVisibility(View.GONE);
if (bind != null) bind.playlistDialogRecyclerView.setVisibility(View.VISIBLE);
playlistDialogHorizontalAdapter.setItems(playlists);

View File

@@ -31,7 +31,8 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel;
private PlaylistCallback playlistCallback;
private final PlaylistCallback playlistCallback;
private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;

View File

@@ -20,7 +20,8 @@ import java.util.Objects;
public class PodcastChannelEditorDialog extends DialogFragment {
private DialogPodcastChannelEditorBinding bind;
private PodcastChannelEditorViewModel podcastChannelEditorViewModel;
private PodcastCallback podcastCallback;
private final PodcastCallback podcastCallback;
private String channelUrl;

View File

@@ -21,7 +21,8 @@ import java.util.Objects;
public class RadioEditorDialog extends DialogFragment {
private DialogRadioEditorBinding bind;
private RadioEditorViewModel radioEditorViewModel;
private RadioCallback radioCallback;
private final RadioCallback radioCallback;
private String radioName;
private String radioStreamURL;

View File

@@ -17,7 +17,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class TrackInfoDialog extends DialogFragment {
private DialogTrackInfoBinding bind;
private MediaMetadata mediaMetadata;
private final MediaMetadata mediaMetadata;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
@@ -28,7 +29,7 @@ public class TrackInfoDialog extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(getActivity())
return new MaterialAlertDialogBuilder(requireActivity())
.setView(bind.getRoot())
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel())
.create();

View File

@@ -52,6 +52,12 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
initData();
}
@Override
public void onDestroy() {
super.onDestroy();
albumCatalogueViewModel.stopLoading();
}
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
activity = (MainActivity) getActivity();
@@ -61,6 +67,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
initAppBar();
initAlbumCatalogueView();
initProgressLoader();
return view;
}
@@ -73,7 +80,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
private void initData() {
albumCatalogueViewModel = new ViewModelProvider(requireActivity()).get(AlbumCatalogueViewModel.class);
albumCatalogueViewModel.loadAlbums(500);
albumCatalogueViewModel.loadAlbums();
}
private void initAppBar() {
@@ -105,7 +112,7 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
bind.albumCatalogueRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumCatalogueRecyclerView.setHasFixedSize(true);
albumAdapter = new AlbumCatalogueAdapter(this);
albumAdapter = new AlbumCatalogueAdapter(this, true);
albumAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
bind.albumCatalogueRecyclerView.setAdapter(albumAdapter);
albumCatalogueViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> albumAdapter.setItems(albums));
@@ -118,6 +125,18 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_album_popup_menu));
}
private void initProgressLoader() {
albumCatalogueViewModel.getLoadingStatus().observe(getViewLifecycleOwner(), isLoading -> {
if (isLoading) {
bind.albumListSortImageView.setEnabled(false);
bind.albumListProgressLoader.setVisibility(View.VISIBLE);
} else {
bind.albumListSortImageView.setEnabled(true);
bind.albumListProgressLoader.setVisibility(View.GONE);
}
});
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.toolbar_menu, menu);
@@ -165,6 +184,9 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
} else if (menuItem.getItemId() == R.id.menu_album_sort_random) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RANDOM);
return true;
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) {
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED);
return true;
}
return false;

View File

@@ -34,6 +34,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
import com.google.android.material.chip.Chip;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
@@ -73,6 +74,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
init();
initAppBar();
initAlbumInfoTextButton();
initAlbumNotes();
initMusicButton();
initBackCover();
initSongsView();
@@ -131,10 +133,20 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind.albumNameLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
bind.albumArtistLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getArtist()));
bind.albumReleaseYearLabel.setText(albumPageViewModel.getAlbum().getYear() != 0 ? String.valueOf(albumPageViewModel.getAlbum().getYear()) : "");
bind.albumSongCountDurationTextview.setText(getString(R.string.album_page_tracks_count_and_duration, albumPageViewModel.getAlbum().getSongCount(), albumPageViewModel.getAlbum().getDuration() != null ? albumPageViewModel.getAlbum().getDuration() / 60 : 0));
bind.albumGenresTextview.setText(albumPageViewModel.getAlbum().getGenre());
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
bind.albumOtherInfoButton.setOnClickListener(v -> {
if (bind.albumDetailView.getVisibility() == View.GONE) {
bind.albumDetailView.setVisibility(View.VISIBLE);
} else if (bind.albumDetailView.getVisibility() == View.VISIBLE) {
bind.albumDetailView.setVisibility(View.GONE);
}
});
}
private void initAlbumInfoTextButton() {
@@ -148,6 +160,18 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
}));
}
private void initAlbumNotes() {
albumPageViewModel.getAlbumInfo().observe(getViewLifecycleOwner(), albumInfo -> {
if (albumInfo != null) {
if (bind != null) bind.albumNotesTextview.setVisibility(View.VISIBLE);
if (bind != null)
bind.albumNotesTextview.setText(MusicUtil.getReadableString(albumInfo.getNotes()));
} else {
if (bind != null) bind.albumNotesTextview.setVisibility(View.GONE);
}
});
}
private void initMusicButton() {
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
if (bind != null && !songs.isEmpty()) {

View File

@@ -27,16 +27,23 @@ import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistCatalogueAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.ArtistPageViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@UnstableApi
public class ArtistPageFragment extends Fragment implements ClickCallback {
private FragmentArtistPageBinding bind;
@@ -44,9 +51,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private ArtistPageViewModel artistPageViewModel;
private SongHorizontalAdapter songHorizontalAdapter;
private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
private AlbumCatalogueAdapter albumCatalogueAdapter;
private ArtistSimilarAdapter artistSimilarAdapter;
private ArtistCatalogueAdapter artistCatalogueAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@@ -63,8 +69,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
initArtistInfo();
initPlayButtons();
initTopSongsView();
initHorizontalAlbumsView();
initVerticalAlbumsView();
initAlbumsView();
initSimilarArtistsView();
return view;
@@ -98,13 +103,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bundle.putParcelable(Constants.ARTIST_OBJECT, artistPageViewModel.getArtist());
activity.navController.navigate(R.id.action_artistPageFragment_to_songListPageFragment, bundle);
});
bind.artistPageAlbumsSwitchLayoutTextViewClickable.setOnClickListener(view -> {
boolean isHorizontalRecyclerViewVisible = bind.albumsHorizontalRecyclerView.getVisibility() == View.VISIBLE;
bind.albumsHorizontalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.GONE : View.VISIBLE);
bind.albumsVerticalRecyclerView.setVisibility(isHorizontalRecyclerViewVisible ? View.VISIBLE : View.GONE);
});
}
private void initAppBar() {
@@ -120,8 +118,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initArtistInfo() {
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artistInfo -> {
if (artistInfo == null) {
if (bind != null)
bind.artistPageBioPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.GONE);
} else {
String normalizedBio = MusicUtil.forceReadableString(artistInfo.getBiography());
@@ -144,8 +140,6 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
startActivity(intent);
});
if (bind != null)
bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
}
});
@@ -154,7 +148,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initPlayButtons() {
bind.artistPageShuffleButton.setOnClickListener(v -> {
artistPageViewModel.getArtistShuffleList().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
@@ -165,7 +159,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.artistPageRadioButton.setOnClickListener(v -> {
artistPageViewModel.getArtistInstantMix().observe(getViewLifecycleOwner(), songs -> {
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
} else {
@@ -182,58 +176,29 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null)
bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageTopSongsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.artistPageShuffleButton.setEnabled(!songs.isEmpty());
songHorizontalAdapter.setItems(songs);
}
});
}
private void initHorizontalAlbumsView() {
bind.albumsHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
private void initAlbumsView() {
bind.albumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.albumsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumsRecyclerView.setHasFixedSize(true);
albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
bind.albumsHorizontalRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumArtistPageOrSimilarAdapter.setItems(albums);
}
});
CustomLinearSnapHelper albumSnapHelper = new CustomLinearSnapHelper();
albumSnapHelper.attachToRecyclerView(bind.albumsHorizontalRecyclerView);
}
private void initVerticalAlbumsView() {
bind.albumsVerticalRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.albumsVerticalRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.albumsVerticalRecyclerView.setHasFixedSize(true);
albumCatalogueAdapter = new AlbumCatalogueAdapter(this);
bind.albumsVerticalRecyclerView.setAdapter(albumCatalogueAdapter);
albumCatalogueAdapter = new AlbumCatalogueAdapter(this, false);
bind.albumsRecyclerView.setAdapter(albumCatalogueAdapter);
artistPageViewModel.getAlbumList().observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.artistPageAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.artistPageAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
albumCatalogueAdapter.setItems(albums);
@@ -242,22 +207,27 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
}
private void initSimilarArtistsView() {
bind.similarArtistsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 2));
bind.similarArtistsRecyclerView.addItemDecoration(new GridItemDecoration(2, 20, false));
bind.similarArtistsRecyclerView.setHasFixedSize(true);
artistSimilarAdapter = new ArtistSimilarAdapter(this);
bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
artistCatalogueAdapter = new ArtistCatalogueAdapter(this);
bind.similarArtistsRecyclerView.setAdapter(artistCatalogueAdapter);
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
if (artist == null) {
if (bind != null)
bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.similarArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
if (bind != null && artist.getSimilarArtists() != null)
bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistSimilarAdapter.setItems(artist.getSimilarArtists());
List<ArtistID3> artists = new ArrayList<>();
if (artist.getSimilarArtists() != null) {
artists.addAll(artist.getSimilarArtists());
}
artistCatalogueAdapter.setItems(artists);
}
});

View File

@@ -33,6 +33,7 @@ import com.cappielloantonio.tempo.viewmodel.DownloadViewModel;
import com.google.android.material.appbar.MaterialToolbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
@@ -110,20 +111,14 @@ public class DownloadFragment extends Fragment implements ClickCallback {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.VISIBLE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.GONE);
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.VISIBLE);
bind.downloadDownloadedSector.setVisibility(View.GONE);
bind.downloadedGroupByImageView.setVisibility(View.GONE);
}
} else {
if (bind != null) {
bind.emptyDownloadLayout.setVisibility(View.GONE);
bind.fragmentDownloadNestedScrollView.setVisibility(View.VISIBLE);
bind.downloadDownloadedPlaceholder.placeholder.setVisibility(View.GONE);
bind.downloadDownloadedSector.setVisibility(View.VISIBLE);
bind.downloadedGroupByImageView.setVisibility(View.VISIBLE);
finishDownloadView(songs);
@@ -165,6 +160,19 @@ public class DownloadFragment extends Fragment implements ClickCallback {
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
setupBackPressing(stack.size());
setupShuffleButton();
});
}
private void setupShuffleButton() {
bind.shuffleDownloadedTextViewClickable.setOnClickListener(view -> {
List<Child> songs = downloadHorizontalAdapter.getShuffling();
if (songs != null && !songs.isEmpty()) {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
});
}

View File

@@ -32,6 +32,7 @@ import com.cappielloantonio.tempo.helper.recyclerview.DotsIndicatorDecoration;
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.HomeSector;
import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
@@ -48,6 +49,7 @@ import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
import com.cappielloantonio.tempo.ui.dialog.HomeRearrangementDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -119,6 +121,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initRecentAddedAlbumView();
initGridView();
initSharesView();
initHomeReorganizer();
reorder();
}
@Override
@@ -156,7 +161,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getRandomShuffleSample().observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
@@ -303,6 +308,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initDiscoverSongSlideView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_DISCOVERY)) return;
bind.discoverSongViewPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
discoverSongAdapter = new DiscoverSongAdapter(this);
@@ -312,12 +319,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
MusicUtil.ratingFilter(songs);
if (songs == null) {
if (bind != null)
bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeDiscoverSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
@@ -329,6 +332,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initSimilarSongView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_MADE_FOR_YOU)) return;
bind.similarTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.similarTracksRecyclerView.setHasFixedSize(true);
@@ -338,12 +343,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
MusicUtil.ratingFilter(songs);
if (songs == null) {
if (bind != null)
bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeSimilarTracksSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
@@ -356,6 +357,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initArtistBestOf() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_BEST_OF)) return;
bind.bestOfArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.bestOfArtistRecyclerView.setHasFixedSize(true);
@@ -363,12 +366,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.bestOfArtistRecyclerView.setAdapter(bestOfArtistAdapter);
homeViewModel.getBestOfArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeBestOfArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
@@ -381,6 +380,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initArtistRadio() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_RADIO_STATION)) return;
bind.radioArtistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.radioArtistRecyclerView.setHasFixedSize(true);
@@ -388,12 +389,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.radioArtistRecyclerView.setAdapter(radioArtistAdapter);
homeViewModel.getStarredArtistsSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRadioArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -408,6 +405,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initGridView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_TOP_SONGS)) return;
bind.gridTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
bind.gridTracksRecyclerView.addItemDecoration(new GridItemDecoration(3, 8, false));
bind.gridTracksRecyclerView.setHasFixedSize(true);
@@ -418,7 +417,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
if (music != null) {
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.size() == 0) {
if (chronologies == null || chronologies.isEmpty()) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
@@ -432,18 +431,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredTracksView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_TRACKS)) return;
bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null)
bind.starredTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredTracksSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -467,18 +464,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredAlbumsView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_ALBUMS)) return;
bind.starredAlbumsRecyclerView.setHasFixedSize(true);
starredAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.starredAlbumsRecyclerView.setAdapter(starredAlbumAdapter);
homeViewModel.getStarredAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -502,18 +497,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initStarredArtistsView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_STARRED_ARTISTS)) return;
bind.starredArtistsRecyclerView.setHasFixedSize(true);
starredArtistAdapter = new ArtistHorizontalAdapter(this);
bind.starredArtistsRecyclerView.setAdapter(starredArtistAdapter);
homeViewModel.getStarredArtists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.starredArtistsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.starredArtistsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -539,18 +532,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initNewReleasesView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_NEW_RELEASES)) return;
bind.newReleasesRecyclerView.setHasFixedSize(true);
newReleasesAlbumAdapter = new AlbumHorizontalAdapter(this, false);
bind.newReleasesRecyclerView.setAdapter(newReleasesAlbumAdapter);
homeViewModel.getRecentlyReleasedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeNewReleasesSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -574,6 +565,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initYearSongView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_FLASHBACK)) return;
bind.yearsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.yearsRecyclerView.setHasFixedSize(true);
@@ -581,12 +574,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.yearsRecyclerView.setAdapter(yearAdapter);
homeViewModel.getYearList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), years -> {
if (years == null) {
if (bind != null)
bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeFlashbackSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
@@ -599,6 +588,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initMostPlayedAlbumView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_MOST_PLAYED)) return;
bind.mostPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.mostPlayedAlbumsRecyclerView.setHasFixedSize(true);
@@ -606,15 +597,10 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.mostPlayedAlbumsRecyclerView.setAdapter(mostPlayedAlbumAdapter);
homeViewModel.getMostPlayedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeMostPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeMostPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeMostPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
// if (albums.size() < 5) reorder();
mostPlayedAlbumAdapter.setItems(albums);
}
@@ -625,6 +611,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initRecentPlayedAlbumView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_LAST_PLAYED)) return;
bind.recentlyPlayedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyPlayedAlbumsRecyclerView.setHasFixedSize(true);
@@ -632,12 +620,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyPlayedAlbumsRecyclerView.setAdapter(recentlyPlayedAlbumAdapter);
homeViewModel.getRecentlyPlayedAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyPlayedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -650,6 +634,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initRecentAddedAlbumView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_RECENTLY_ADDED)) return;
bind.recentlyAddedAlbumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
bind.recentlyAddedAlbumsRecyclerView.setHasFixedSize(true);
@@ -657,12 +643,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.recentlyAddedAlbumsRecyclerView.setAdapter(recentlyAddedAlbumAdapter);
homeViewModel.getMostRecentlyAddedAlbums(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.homeRecentlyAddedAlbumsSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -675,6 +657,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
}
private void initSharesView() {
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_SHARED)) return;
bind.sharesRecyclerView.setHasFixedSize(true);
shareHorizontalAdapter = new ShareHorizontalAdapter(this);
@@ -682,11 +666,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
if (Preferences.isSharingEnabled()) {
homeViewModel.getShares(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), shares -> {
if (shares == null) {
if (bind != null)
bind.sharesPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.sharesSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.sharesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.sharesSector.setVisibility(!shares.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
@@ -710,6 +691,17 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
);
}
private void initHomeReorganizer() {
final Handler handler = new Handler();
final Runnable runnable = () -> { if (bind != null) bind.homeSectorRearrangementButton.setVisibility(View.VISIBLE); };
handler.postDelayed(runnable, 5000);
bind.homeSectorRearrangementButton.setOnClickListener(v -> {
HomeRearrangementDialog dialog = new HomeRearrangementDialog();
dialog.show(requireActivity().getSupportFragmentManager(), null);
});
}
private void refreshSharesView() {
final Handler handler = new Handler();
final Runnable runnable = () -> {
@@ -735,6 +727,63 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
});
}
public void reorder() {
if (bind != null && homeViewModel.getHomeSectorList() != null) {
bind.homeLinearLayoutContainer.removeAllViews();
for (HomeSector sector : homeViewModel.getHomeSectorList()) {
if (!sector.isVisible()) continue;
switch (sector.getId()) {
case Constants.HOME_SECTOR_DISCOVERY:
bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
break;
case Constants.HOME_SECTOR_MADE_FOR_YOU:
bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
break;
case Constants.HOME_SECTOR_BEST_OF:
bind.homeLinearLayoutContainer.addView(bind.homeBestOfArtistSector);
break;
case Constants.HOME_SECTOR_RADIO_STATION:
bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
break;
case Constants.HOME_SECTOR_TOP_SONGS:
bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
break;
case Constants.HOME_SECTOR_STARRED_TRACKS:
bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
break;
case Constants.HOME_SECTOR_STARRED_ALBUMS:
bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
break;
case Constants.HOME_SECTOR_STARRED_ARTISTS:
bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
break;
case Constants.HOME_SECTOR_NEW_RELEASES:
bind.homeLinearLayoutContainer.addView(bind.homeNewReleasesSector);
break;
case Constants.HOME_SECTOR_FLASHBACK:
bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
break;
case Constants.HOME_SECTOR_MOST_PLAYED:
bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
break;
case Constants.HOME_SECTOR_LAST_PLAYED:
bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
break;
case Constants.HOME_SECTOR_RECENTLY_ADDED:
bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
break;
case Constants.HOME_SECTOR_SHARED:
bind.homeLinearLayoutContainer.addView(bind.sharesSector);
break;
}
}
bind.homeLinearLayoutContainer.addView(bind.homeSectorRearrangementButton);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
@@ -753,7 +802,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getMediaInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.TRACK_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs != null && songs.size() > 0) {
if (songs != null && !songs.isEmpty()) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
}
});
@@ -794,7 +843,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getArtistInstantMix(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}
@@ -805,7 +854,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.getArtistBestOf(getViewLifecycleOwner(), bundle.getParcelable(Constants.ARTIST_OBJECT)).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
activity.setBottomSheetInPeek(true);
}

View File

@@ -142,12 +142,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.musicFolderRecyclerView.setAdapter(musicFolderAdapter);
libraryViewModel.getMusicFolders(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), musicFolders -> {
if (musicFolders == null) {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryMusicFolderSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryMusicFolderPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryMusicFolderSector.setVisibility(!musicFolders.isEmpty() ? View.VISIBLE : View.GONE);
@@ -164,11 +160,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.albumRecyclerView.setAdapter(albumAdapter);
libraryViewModel.getAlbumSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
if (albums == null) {
if (bind != null)
bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryAlbumSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryAlbumPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryAlbumSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
@@ -188,12 +181,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.artistRecyclerView.setAdapter(artistAdapter);
libraryViewModel.getArtistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
if (artists == null) {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryArtistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
@@ -214,11 +203,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
libraryViewModel.getGenreSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), genres -> {
if (genres == null) {
if (bind != null)
bind.libraryGenrePlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryGenresSector.setVisibility(View.GONE);
} else {
if (bind != null) bind.libraryGenrePlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryGenresSector.setVisibility(!genres.isEmpty() ? View.VISIBLE : View.GONE);
@@ -238,12 +224,8 @@ public class LibraryFragment extends Fragment implements ClickCallback {
bind.playlistRecyclerView.setAdapter(playlistHorizontalAdapter);
libraryViewModel.getPlaylistSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
if (playlists == null) {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.VISIBLE);
if (bind != null) bind.libraryPlaylistSector.setVisibility(View.GONE);
} else {
if (bind != null)
bind.libraryPlaylistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.libraryPlaylistSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);

View File

@@ -92,7 +92,7 @@ public class LoginFragment extends Fragment implements ClickCallback {
serverAdapter = new ServerAdapter(this);
bind.serverListRecyclerView.setAdapter(serverAdapter);
loginViewModel.getServerList().observe(getViewLifecycleOwner(), servers -> {
if (servers.size() > 0) {
if (!servers.isEmpty()) {
if (bind != null) bind.noServerAddedTextView.setVisibility(View.GONE);
if (bind != null) bind.serverListRecyclerView.setVisibility(View.VISIBLE);
serverAdapter.setItems(servers);

View File

@@ -249,6 +249,11 @@ public class PlayerBottomSheetFragment extends Fragment {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
}
public void setPlayerControllerVerticalPagerDraggableState(Boolean isDraggable) {
ViewPager2 playerControllerVerticalPager = (ViewPager2) bind.playerBodyLayout.playerBodyBottomSheetViewPager;
playerControllerVerticalPager.setUserInputEnabled(isDraggable);
}
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler();
progressBarRunnable = () -> {

View File

@@ -257,10 +257,20 @@ public class PlayerControllerFragment extends Fragment {
public void onPageSelected(int position) {
super.onPageSelected(position);
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (position == 0) {
activity.setBottomSheetDraggableState(true);
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.setPlayerControllerVerticalPagerDraggableState(true);
}
} else if (position == 1) {
activity.setBottomSheetDraggableState(false);
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.setPlayerControllerVerticalPagerDraggableState(false);
}
}
}
});

View File

@@ -1,31 +1,60 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerLyricsBinding;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Line;
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.OpenSubsonicExtensionsUtil;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
@OptIn(markerClass = UnstableApi.class)
public class PlayerLyricsFragment extends Fragment {
private static final String TAG = "PlayerLyricsFragment";
private InnerFragmentPlayerLyricsBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private MediaBrowser mediaBrowser;
private Handler syncLyricsHandler;
private Runnable syncLyricsRunnable;
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerLyricsBinding.inflate(inflater, container, false);
View view = bind.getRoot();
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
initOverlay();
return view;
}
@@ -33,7 +62,32 @@ public class PlayerLyricsFragment extends Fragment {
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initLyrics();
initPanelContent();
}
@Override
public void onStart() {
super.onStart();
initializeBrowser();
}
@Override
public void onResume() {
super.onResume();
bindMediaController();
}
@Override
public void onPause() {
super.onPause();
releaseHandler();
}
@Override
public void onStop() {
releaseBrowser();
super.onStop();
}
@Override
@@ -42,27 +96,195 @@ public class PlayerLyricsFragment extends Fragment {
bind = null;
}
private void initLyrics() {
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
playerBottomSheetViewModel.getLiveDescription().observe(getViewLifecycleOwner(), description -> {
if (bind != null) {
if (lyrics != null && !lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
} else if (description != null && !description.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(description));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
} else {
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
}
}
});
private void initOverlay() {
bind.syncLyricsTapButton.setOnClickListener(view -> {
playerBottomSheetViewModel.changeSyncLyricsState();
});
}
private void initializeBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseHandler() {
if (syncLyricsHandler != null) {
syncLyricsHandler.removeCallbacks(syncLyricsRunnable);
syncLyricsHandler = null;
}
}
private void releaseBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
mediaBrowser = mediaBrowserListenableFuture.get();
defineProgressHandler();
} catch (Exception e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void initPanelContent() {
if (OpenSubsonicExtensionsUtil.isSongLyricsExtensionAvailable()) {
playerBottomSheetViewModel.getLiveLyricsList().observe(getViewLifecycleOwner(), lyricsList -> {
setPanelContent(null, lyricsList);
});
} else {
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
setPanelContent(lyrics, null);
});
}
}
private void setPanelContent(String lyrics, LyricsList lyricsList) {
playerBottomSheetViewModel.getLiveDescription().observe(getViewLifecycleOwner(), description -> {
if (bind != null) {
bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, 0);
if (lyrics != null && !lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
bind.syncLyricsTapButton.setVisibility(View.GONE);
} else if (lyricsList != null && lyricsList.getStructuredLyrics() != null) {
setSyncLirics(lyricsList);
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
bind.syncLyricsTapButton.setVisibility(View.VISIBLE);
} else if (description != null && !description.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(description));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
bind.syncLyricsTapButton.setVisibility(View.GONE);
} else {
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
bind.syncLyricsTapButton.setVisibility(View.GONE);
}
}
});
}
@SuppressLint("DefaultLocale")
private void setSyncLirics(LyricsList lyricsList) {
if (lyricsList.getStructuredLyrics() != null && !lyricsList.getStructuredLyrics().isEmpty() && lyricsList.getStructuredLyrics().get(0).getLine() != null) {
StringBuilder lyricsBuilder = new StringBuilder();
List<Line> lines = lyricsList.getStructuredLyrics().get(0).getLine();
if (lines != null) {
for (Line line : lines) {
lyricsBuilder.append(line.getValue().trim()).append("\n");
}
}
bind.nowPlayingSongLyricsTextView.setText(lyricsBuilder.toString());
}
}
private void defineProgressHandler() {
playerBottomSheetViewModel.getLiveLyricsList().observe(getViewLifecycleOwner(), lyricsList -> {
if (lyricsList != null) {
if (lyricsList.getStructuredLyrics() != null && lyricsList.getStructuredLyrics().get(0) != null && !lyricsList.getStructuredLyrics().get(0).getSynced()) {
releaseHandler();
return;
}
syncLyricsHandler = new Handler();
syncLyricsRunnable = () -> {
if (syncLyricsHandler != null) {
if (bind != null) {
displaySyncedLyrics();
}
syncLyricsHandler.postDelayed(syncLyricsRunnable, 250);
}
};
syncLyricsHandler.postDelayed(syncLyricsRunnable, 250);
} else {
releaseHandler();
}
});
}
private void displaySyncedLyrics() {
LyricsList lyricsList = playerBottomSheetViewModel.getLiveLyricsList().getValue();
int timestamp = (int) (mediaBrowser.getCurrentPosition());
if (lyricsList != null && lyricsList.getStructuredLyrics() != null && !lyricsList.getStructuredLyrics().isEmpty() && lyricsList.getStructuredLyrics().get(0).getLine() != null) {
StringBuilder lyricsBuilder = new StringBuilder();
List<Line> lines = lyricsList.getStructuredLyrics().get(0).getLine();
if (lines == null || lines.isEmpty()) return;
for (Line line : lines) {
lyricsBuilder.append(line.getValue().trim()).append("\n");
}
Line toHighlight = lines.stream().filter(line -> line != null && line.getStart() != null && line.getStart() < timestamp).reduce((first, second) -> second).orElse(null);
if (toHighlight != null) {
String lyrics = lyricsBuilder.toString();
Spannable spannableString = new SpannableString(lyrics);
int startingPosition = getStartPosition(lines, toHighlight);
int endingPosition = startingPosition + toHighlight.getValue().length();
spannableString.setSpan(new ForegroundColorSpan(requireContext().getResources().getColor(R.color.shadowsLyricsTextColor, null)), 0, lyrics.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
spannableString.setSpan(new ForegroundColorSpan(requireContext().getResources().getColor(R.color.lyricsTextColor, null)), startingPosition, endingPosition, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
bind.nowPlayingSongLyricsTextView.setText(spannableString);
if (playerBottomSheetViewModel.getSyncLyricsState()) {
bind.nowPlayingSongLyricsSrollView.smoothScrollTo(0, getScroll(lines, toHighlight));
}
}
}
}
private int getStartPosition(List<Line> lines, Line toHighlight) {
int start = 0;
for (Line line : lines) {
if (line != toHighlight) {
start = start + line.getValue().length() + 1;
} else {
break;
}
}
return start;
}
private int getLineCount(List<Line> lines, Line toHighlight) {
int start = 0;
for (Line line : lines) {
if (line != toHighlight) {
bind.tempLyricsLineTextView.setText(line.getValue());
start = start + bind.tempLyricsLineTextView.getLineCount();
} else {
break;
}
}
return start;
}
private int getScroll(List<Line> lines, Line toHighlight) {
int lineHeight = bind.nowPlayingSongLyricsTextView.getLineHeight();
int lineCount = getLineCount(lines, toHighlight);
int scrollViewHeight = bind.nowPlayingSongLyricsSrollView.getHeight();
return lineHeight * lineCount < scrollViewHeight / 2 ? 0 : lineHeight * lineCount - scrollViewHeight / 2 + lineHeight;
}
}

View File

@@ -167,7 +167,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
// Pic top-left
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.from(requireContext(), !songs.isEmpty() ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
.into(bind.playlistCoverImageViewTopLeft);

View File

@@ -6,6 +6,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
@@ -91,6 +92,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
actionSyncStarredTracks();
actionChangeDownloadStorage();
actionDeleteDownloadStorage();
actionKeepScreenOn();
}
@Override
@@ -248,4 +250,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
}
});
}
private void actionKeepScreenOn() {
findPreference("always_on_display").setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Boolean) {
if ((Boolean) newValue) {
activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
return true;
});
}
}

View File

@@ -117,7 +117,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
public void onLoadMedia(List<?> media) {
MusicUtil.ratingFilter((ArrayList<Child>) media);
if (media.size() > 0) {
if (!media.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, (ArrayList<Child>) media, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}

View File

@@ -91,7 +91,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
artistRepository.getInstantMix(artist, 20).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
}
@@ -106,7 +106,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
artistRepository.getRandomSong(artist, 50).observe(getViewLifecycleOwner(), songs -> {
MusicUtil.ratingFilter(songs);
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);

View File

@@ -121,7 +121,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
return;
}
if (songs.size() > 0) {
if (!songs.isEmpty()) {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
dismissBottomSheet();
}

View File

@@ -30,6 +30,7 @@ object Constants {
const val ALBUM_ORDER_BY_ARTIST = "ALBUM_ORDER_BY_ARTIST"
const val ALBUM_ORDER_BY_YEAR = "ALBUM_ORDER_BY_YEAR"
const val ALBUM_ORDER_BY_RANDOM = "ALBUM_ORDER_BY_RANDOM"
const val ALBUM_ORDER_BY_RECENTLY_ADDED = "ALBUM_ORDER_BY_RECENTLY_ADDED"
const val ARTIST_DOWNLOADED = "ARTIST_DOWNLOADED"
const val ARTIST_STARRED = "ARTIST_STARRED"
@@ -90,4 +91,19 @@ object Constants {
const val PLAYABLE_MEDIA_LIMIT = 100
const val PRE_PLAYABLE_MEDIA = 15
const val HOME_SECTOR_DISCOVERY = "HOME_SECTOR_DISCOVERY"
const val HOME_SECTOR_MADE_FOR_YOU = "HOME_SECTOR_MADE_FOR_YOU"
const val HOME_SECTOR_BEST_OF = "HOME_SECTOR_BEST_OF"
const val HOME_SECTOR_RADIO_STATION = "HOME_SECTOR_RADIO_STATION"
const val HOME_SECTOR_TOP_SONGS = "HOME_SECTOR_TOP_SONGS"
const val HOME_SECTOR_STARRED_TRACKS = "HOME_SECTOR_STARRED_TRACKS"
const val HOME_SECTOR_STARRED_ALBUMS = "HOME_SECTOR_STARRED_ALBUMS"
const val HOME_SECTOR_STARRED_ARTISTS = "HOME_SECTOR_STARRED_ARTISTS"
const val HOME_SECTOR_NEW_RELEASES = "HOME_SECTOR_NEW_RELEASES"
const val HOME_SECTOR_FLASHBACK = "HOME_SECTOR_FLASHBACK"
const val HOME_SECTOR_MOST_PLAYED = "HOME_SECTOR_MOST_PLAYED"
const val HOME_SECTOR_LAST_PLAYED = "HOME_SECTOR_LAST_PLAYED"
const val HOME_SECTOR_RECENTLY_ADDED = "HOME_SECTOR_RECENTLY_ADDED"
const val HOME_SECTOR_SHARED = "HOME_SECTOR_SHARED"
}

View File

@@ -128,16 +128,18 @@ public class MusicUtil {
}
public static String getReadableDurationString(long duration, boolean millis) {
public static String getReadableDurationString(Long duration, boolean millis) {
long lenght = duration != null ? duration : 0;
long minutes;
long seconds;
if (millis) {
minutes = (duration / 1000) / 60;
seconds = (duration / 1000) % 60;
minutes = (lenght / 1000) / 60;
seconds = (lenght / 1000) % 60;
} else {
minutes = duration / 60;
seconds = duration % 60;
minutes = lenght / 60;
seconds = lenght % 60;
}
if (minutes < 60) {
@@ -149,6 +151,22 @@ public class MusicUtil {
}
}
public static String getReadableDurationString(Integer duration, boolean millis) {
long lenght = duration != null ? duration : 0;
return getReadableDurationString(lenght, millis);
}
public static String getReadableAudioQualityString(Child child) {
if (!Preferences.showAudioQuality()) return "";
return "" +
" " +
child.getBitrate() +
"kbps" +
" " +
child.getSuffix();
}
public static String getReadablePodcastDurationString(long duration) {
long minutes = duration / 60;
@@ -204,7 +222,7 @@ public class MusicUtil {
public static List<String> getReadableStrings(List<String> strings) {
List<String> readableStrings = new ArrayList<>();
if (strings.size() > 0) {
if (!strings.isEmpty()) {
for (String string : strings) {
if (string != null) {
readableStrings.add(Html.fromHtml(string, Html.FROM_HTML_MODE_COMPACT).toString());
@@ -320,4 +338,4 @@ public class MusicUtil {
toFilter.addAll(filtered);
}
}
}

View File

@@ -0,0 +1,41 @@
package com.cappielloantonio.tempo.util;
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.util.List;
public class OpenSubsonicExtensionsUtil {
private static List<OpenSubsonicExtension> getOpenSubsonicExtensions() {
List<OpenSubsonicExtension> extensions = null;
if (Preferences.isOpenSubsonic() && Preferences.getOpenSubsonicExtensions() != null) {
extensions = new Gson().fromJson(
Preferences.getOpenSubsonicExtensions(),
new TypeToken<List<OpenSubsonicExtension>>() {
}.getType()
);
}
return extensions;
}
private static OpenSubsonicExtension getOpenSubsonicExtension(String extensionName) {
if (getOpenSubsonicExtensions() == null) return null;
return getOpenSubsonicExtensions().stream().filter(openSubsonicExtension -> openSubsonicExtension.getName().equals(extensionName)).findAny().orElse(null);
}
public static boolean isTranscodeOffsetExtensionAvailable() {
return getOpenSubsonicExtension("transcodeOffset") != null;
}
public static boolean isFormPostExtensionAvailable() {
return getOpenSubsonicExtension("formPost") != null;
}
public static boolean isSongLyricsExtensionAvailable() {
return getOpenSubsonicExtension("songLyrics") != null;
}
}

View File

@@ -1,6 +1,12 @@
package com.cappielloantonio.tempo.util
import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.model.HomeSector
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension
import com.google.gson.Gson
object Preferences {
const val THEME = "theme"
@@ -12,6 +18,8 @@ object Preferences {
private const val LOW_SECURITY = "low_security"
private const val BATTERY_OPTIMIZATION = "battery_optimization"
private const val SERVER_ID = "server_id"
private const val OPEN_SUBSONIC = "open_subsonic"
private const val OPEN_SUBSONIC_EXTENSIONS = "open_subsonic_extensions"
private const val PLAYBACK_SPEED = "playback_speed"
private const val SKIP_SILENCE = "skip_silence"
private const val IMAGE_CACHE_SIZE = "image_cache_size"
@@ -45,6 +53,10 @@ object Preferences {
private const val BUFFERING_STRATEGY = "buffering_strategy"
private const val SKIP_MIN_STAR_RATING = "skip_min_star_rating"
private const val MIN_STAR_RATING = "min_star_rating"
private const val ALWAYS_ON_DISPLAY = "always_on_display"
private const val AUDIO_QUALITY_PER_ITEM = "audio_quality_per_item"
private const val HOME_SECTOR_LIST = "home_sector_list"
private const val RATING_PER_ITEM = "rating_per_item"
@JvmStatic
@@ -117,6 +129,26 @@ object Preferences {
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
}
@JvmStatic
fun isOpenSubsonic(): Boolean {
return App.getInstance().preferences.getBoolean(OPEN_SUBSONIC, false)
}
@JvmStatic
fun setOpenSubsonic(isOpenSubsonic: Boolean) {
App.getInstance().preferences.edit().putBoolean(OPEN_SUBSONIC, isOpenSubsonic).apply()
}
@JvmStatic
fun getOpenSubsonicExtensions(): String? {
return App.getInstance().preferences.getString(OPEN_SUBSONIC_EXTENSIONS, null)
}
@JvmStatic
fun setOpenSubsonicExtensions(extension: List<OpenSubsonicExtension>) {
App.getInstance().preferences.edit().putString(OPEN_SUBSONIC_EXTENSIONS, Gson().toJson(extension)).apply()
}
@JvmStatic
fun askForOptimization(): Boolean {
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
@@ -345,4 +377,29 @@ object Preferences {
fun getMinStarRatingAccepted(): Int {
return App.getInstance().preferences.getInt(MIN_STAR_RATING, 0)
}
@JvmStatic
fun isDisplayAlwaysOn(): Boolean {
return App.getInstance().preferences.getBoolean(ALWAYS_ON_DISPLAY, false)
}
@JvmStatic
fun showAudioQuality(): Boolean {
return App.getInstance().preferences.getBoolean(AUDIO_QUALITY_PER_ITEM, false)
}
@JvmStatic
fun getHomeSectorList(): String? {
return App.getInstance().preferences.getString(HOME_SECTOR_LIST, null)
}
@JvmStatic
fun setHomeSectorList(extension: List<HomeSector>?) {
App.getInstance().preferences.edit().putString(HOME_SECTOR_LIST, Gson().toJson(extension)).apply()
}
@JvmStatic
fun showItemRating(): Boolean {
return App.getInstance().preferences.getBoolean(RATING_PER_ITEM, false)
}
}

View File

@@ -102,7 +102,7 @@ public class ReplayGainUtil {
private static Float parseReplayGainTag(Metadata.Entry entry) {
try {
return Float.parseFloat(entry.toString().replaceAll("[^\\d.]", ""));
return Float.parseFloat(entry.toString().replaceAll("[^\\d.-]", ""));
} catch (NumberFormatException exception) {
return 0f;
}

View File

@@ -20,8 +20,10 @@ import retrofit2.Callback;
public class AlbumCatalogueViewModel extends AndroidViewModel {
private final MutableLiveData<List<AlbumID3>> albumList = new MutableLiveData<>(new ArrayList<>());
private final MutableLiveData<Boolean> loading = new MutableLiveData<>(true);
private int page = 0;
private Status status = Status.STOPPED;
public AlbumCatalogueViewModel(@NonNull Application application) {
super(application);
@@ -31,7 +33,22 @@ public class AlbumCatalogueViewModel extends AndroidViewModel {
return albumList;
}
public void loadAlbums(int size) {
public LiveData<Boolean> getLoadingStatus() {
return loading;
}
public void loadAlbums() {
page = 0;
status = Status.RUNNING;
albumList.setValue(new ArrayList<>());
loadAlbums(500);
}
public void stopLoading() {
status = Status.STOPPED;
}
private void loadAlbums(int size) {
retrieveAlbums(new MediaCallback() {
@Override
public void onError(Exception exception) {
@@ -39,15 +56,22 @@ public class AlbumCatalogueViewModel extends AndroidViewModel {
@Override
public void onLoadMedia(List<?> media) {
List<AlbumID3> liveAlbum = albumList.getValue();
if (status == Status.STOPPED) {
loading.setValue(false);
return;
}
if (liveAlbum == null) liveAlbum = new ArrayList<>();
List<AlbumID3> liveAlbum = albumList.getValue();
liveAlbum.addAll((List<AlbumID3>) media);
albumList.setValue(liveAlbum);
if (media.size() == size) {
loadAlbums(size);
loading.setValue(true);
} else {
status = Status.STOPPED;
loading.setValue(false);
}
}
}, size, size * page++);
@@ -73,4 +97,9 @@ public class AlbumCatalogueViewModel extends AndroidViewModel {
}
});
}
}
private enum Status {
RUNNING,
STOPPED
}
}

View File

@@ -9,6 +9,7 @@ import androidx.lifecycle.LiveData;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
@@ -42,4 +43,8 @@ public class AlbumPageViewModel extends AndroidViewModel {
public LiveData<ArtistID3> getArtist() {
return artistRepository.getArtistInfo(album.getArtistId());
}
public LiveData<AlbumInfo> getAlbumInfo() {
return albumRepository.getAlbumInfo(album.getId());
}
}

View File

@@ -0,0 +1,77 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.model.HomeSector;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.List;
public class HomeRearrangementViewModel extends AndroidViewModel {
private List<HomeSector> sectors = new ArrayList<>();
public HomeRearrangementViewModel(@NonNull Application application) {
super(application);
}
public List<HomeSector> getHomeSectorList() {
if (sectors != null && !sectors.isEmpty()) return sectors;
if (Preferences.getHomeSectorList() != null && !Preferences.getHomeSectorList().equals("null")) {
sectors = new Gson().fromJson(
Preferences.getHomeSectorList(),
new TypeToken<List<HomeSector>>() {
}.getType()
);
} else {
sectors = fillStandardHomeSectorList();
}
return sectors;
}
public void orderSectorLiveListAfterSwap(List<HomeSector> sectors) {
this.sectors = sectors;
}
public void saveHomeSectorList(List<HomeSector> sectors) {
Preferences.setHomeSectorList(sectors);
}
public void resetHomeSectorList() {
Preferences.setHomeSectorList(null);
}
public void closeDialog() {
sectors = null;
}
private List<HomeSector> fillStandardHomeSectorList() {
List<HomeSector> sectors = new ArrayList<>();
sectors.add(new HomeSector(Constants.HOME_SECTOR_DISCOVERY, getApplication().getString(R.string.home_title_discovery), true, 1));
sectors.add(new HomeSector(Constants.HOME_SECTOR_MADE_FOR_YOU, getApplication().getString(R.string.home_title_made_for_you), true, 2));
sectors.add(new HomeSector(Constants.HOME_SECTOR_BEST_OF, getApplication().getString(R.string.home_title_best_of), true, 3));
sectors.add(new HomeSector(Constants.HOME_SECTOR_RADIO_STATION, getApplication().getString(R.string.home_title_radio_station), true, 4));
sectors.add(new HomeSector(Constants.HOME_SECTOR_TOP_SONGS, getApplication().getString(R.string.home_title_top_songs), true, 5));
sectors.add(new HomeSector(Constants.HOME_SECTOR_STARRED_TRACKS, getApplication().getString(R.string.home_title_starred_tracks), true, 6));
sectors.add(new HomeSector(Constants.HOME_SECTOR_STARRED_ALBUMS, getApplication().getString(R.string.home_title_starred_albums), true, 7));
sectors.add(new HomeSector(Constants.HOME_SECTOR_STARRED_ARTISTS, getApplication().getString(R.string.home_title_starred_artists), true, 8));
sectors.add(new HomeSector(Constants.HOME_SECTOR_NEW_RELEASES, getApplication().getString(R.string.home_title_new_releases), true, 9));
sectors.add(new HomeSector(Constants.HOME_SECTOR_FLASHBACK, getApplication().getString(R.string.home_title_flashback), true, 10));
sectors.add(new HomeSector(Constants.HOME_SECTOR_MOST_PLAYED, getApplication().getString(R.string.home_title_most_played), true, 11));
sectors.add(new HomeSector(Constants.HOME_SECTOR_LAST_PLAYED, getApplication().getString(R.string.home_title_last_played), true, 12));
sectors.add(new HomeSector(Constants.HOME_SECTOR_RECENTLY_ADDED, getApplication().getString(R.string.home_title_recently_added), true, 13));
sectors.add(new HomeSector(Constants.HOME_SECTOR_SHARED, getApplication().getString(R.string.home_title_shares), true, 14));
return sectors;
}
}

View File

@@ -11,6 +11,7 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Favorite;
import com.cappielloantonio.tempo.model.HomeSector;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.ChronologyRepository;
@@ -22,6 +23,8 @@ import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.reflect.TypeToken;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Calendar;
@@ -59,10 +62,13 @@ public class HomeViewModel extends AndroidViewModel {
private final MutableLiveData<List<Child>> artistBestOf = new MutableLiveData<>(null);
private final MutableLiveData<List<Share>> shares = new MutableLiveData<>(null);
private List<HomeSector> sectors;
public HomeViewModel(@NonNull Application application) {
super(application);
setHomeSectorList();
songRepository = new SongRepository();
albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository();
@@ -266,6 +272,26 @@ public class HomeViewModel extends AndroidViewModel {
sharingRepository.getShares().observe(owner, this.shares::postValue);
}
private void setHomeSectorList() {
if (Preferences.getHomeSectorList() != null && !Preferences.getHomeSectorList().equals("null")) {
sectors = new Gson().fromJson(
Preferences.getHomeSectorList(),
new TypeToken<List<HomeSector>>() {
}.getType()
);
}
}
public List<HomeSector> getHomeSectorList() {
return sectors;
}
public boolean checkHomeSectorVisibility(String sectorId) {
return sectors != null && sectors.stream().filter(sector -> sector.getId().equals(sectorId))
.findAny()
.orElse(null) == null;
}
public void setOfflineFavorite() {
ArrayList<Favorite> favorites = getFavorites();
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);

View File

@@ -15,8 +15,6 @@ public class IndexViewModel extends AndroidViewModel {
private MusicFolder musicFolder;
private MutableLiveData<Indexes> indexes = new MutableLiveData<>(null);
public IndexViewModel(@NonNull Application application) {
super(application);

View File

@@ -8,6 +8,10 @@ import androidx.lifecycle.LiveData;
import com.cappielloantonio.tempo.repository.QueueRepository;
import com.cappielloantonio.tempo.repository.SystemRepository;
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
import java.util.List;
public class MainViewModel extends AndroidViewModel {
private static final String TAG = "SearchViewModel";
@@ -25,7 +29,11 @@ public class MainViewModel extends AndroidViewModel {
return queueRepository.count() != 0;
}
public LiveData<Boolean> ping() {
public LiveData<SubsonicResponse> ping() {
return systemRepository.ping();
}
public LiveData<List<OpenSubsonicExtension>> getOpenSubsonicExtensions() {
return systemRepository.getOpenSubsonicExtensions();
}
}

View File

@@ -16,15 +16,18 @@ import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.OpenRepository;
import com.cappielloantonio.tempo.repository.QueueRepository;
import com.cappielloantonio.tempo.repository.SongRepository;
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.NetworkUtil;
import com.cappielloantonio.tempo.util.OpenSubsonicExtensionsUtil;
import com.cappielloantonio.tempo.util.Preferences;
import java.util.Collections;
@@ -40,13 +43,14 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
private final ArtistRepository artistRepository;
private final QueueRepository queueRepository;
private final FavoriteRepository favoriteRepository;
private final OpenRepository openRepository;
private final MutableLiveData<String> lyricsLiveData = new MutableLiveData<>(null);
private final MutableLiveData<LyricsList> lyricsListLiveData = new MutableLiveData<>(null);
private final MutableLiveData<String> descriptionLiveData = new MutableLiveData<>(null);
private final MutableLiveData<Child> liveMedia = new MutableLiveData<>(null);
private final MutableLiveData<ArtistID3> liveArtist = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
private boolean lyricsSyncState = true;
public PlayerBottomSheetViewModel(@NonNull Application application) {
@@ -56,6 +60,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
artistRepository = new ArtistRepository();
queueRepository = new QueueRepository();
favoriteRepository = new FavoriteRepository();
openRepository = new OpenRepository();
}
public LiveData<List<Queue>> getQueueSong() {
@@ -125,8 +130,18 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
return lyricsLiveData;
}
public LiveData<LyricsList> getLiveLyricsList() {
return lyricsListLiveData;
}
public void refreshMediaInfo(LifecycleOwner owner, Child media) {
songRepository.getSongLyrics(media).observe(owner, lyricsLiveData::postValue);
if (OpenSubsonicExtensionsUtil.isSongLyricsExtensionAvailable()) {
openRepository.getLyricsBySongId(media.getId()).observe(owner, lyricsListLiveData::postValue);
lyricsLiveData.postValue(null);
} else {
songRepository.getSongLyrics(media).observe(owner, lyricsLiveData::postValue);
lyricsListLiveData.postValue(null);
}
}
public LiveData<Child> getLiveMedia() {
@@ -196,4 +211,12 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
return false;
}
public void changeSyncLyricsState() {
lyricsSyncState = !lyricsSyncState;
}
public boolean getSyncLyricsState() {
return lyricsSyncState;
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M480,600L280,400L680,400L480,600Z"/>
</vector>

View File

@@ -1,9 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M19,9H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM5,15h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z" />
</vector>
android:pathData="M228.29,600Q213,600 202.5,589.71Q192,579.42 192,564.21Q192,549 202.34,538.5Q212.69,528 227.98,528L731.71,528Q747,528 757.5,538.29Q768,548.58 768,563.79Q768,579 757.66,589.5Q747.31,600 732.02,600L228.29,600ZM228.29,432Q213,432 202.5,421.71Q192,411.42 192,396.21Q192,381 202.34,370.5Q212.69,360 227.98,360L731.71,360Q747,360 757.5,370.29Q768,380.58 768,395.79Q768,411 757.66,421.5Q747.31,432 732.02,432L228.29,432Z" />
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorOnPrimaryContainer"
android:fillColor="@color/titleTextColor"
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
</vector>

View File

@@ -4,6 +4,6 @@
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorOnPrimaryContainer"
android:fillColor="@color/titleTextColor"
android:pathData="M16.5,3c-1.74,0 -3.41,0.81 -4.5,2.09C10.91,3.81 9.24,3 7.5,3 4.42,3 2,5.42 2,8.5c0,3.78 3.4,6.86 8.55,11.54L12,21.35l1.45,-1.32C18.6,15.36 22,12.28 22,8.5 22,5.42 19.58,3 16.5,3zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M160,800L160,720L269,720Q218,676 189,614Q160,552 160,480Q160,368 228,282.5Q296,197 400,170L400,254Q330,279 285,340.5Q240,402 240,480Q240,534 261.5,579.5Q283,625 320,658L320,560L400,560L400,800L160,800ZM720,480Q720,429 699.5,384.5Q679,340 640,302L640,400L560,400L560,160L800,160L800,240L691,240Q750,293 774.5,353.5Q799,414 800,480L720,480ZM640,880Q623,880 611.5,868.5Q600,857 600,840L600,720Q600,703 611.5,691.5Q623,680 640,680L640,680L640,640Q640,607 663.5,583.5Q687,560 720,560Q753,560 776.5,583.5Q800,607 800,640L800,680L800,680Q817,680 828.5,691.5Q840,703 840,720L840,840Q840,857 828.5,868.5Q817,880 800,880L640,880ZM680,680L760,680L760,640Q760,623 748.5,611.5Q737,600 720,600Q703,600 691.5,611.5Q680,623 680,640L680,680Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:height="24dp"
android:viewportHeight="960"
android:viewportWidth="960"
android:width="24dp">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M233,840L298,559L80,370L368,345L480,80L592,345L880,370L662,559L727,840L480,691L233,840Z" />
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M354,673L480,597L606,674L573,530L684,434L538,421L480,285L422,420L276,433L387,530L354,673ZM233,840L298,559L80,370L368,345L480,80L592,345L880,370L662,559L727,840L480,691L233,840ZM480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490L480,490Z" />
</vector>

View File

@@ -0,0 +1,23 @@
<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="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="4dp"
android:text="@string/home_rearrangement_dialog_subtitle" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/home_sector_item_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingHorizontal="18dp" />
</LinearLayout>

View File

@@ -1,98 +1,117 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
<androidx.core.widget.NestedScrollView
android:id="@+id/home_radio_station_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server_name_text_view"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_name"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
android:orientation="vertical">
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server_name_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_name"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_username"
android:inputType="textShortMessage"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="password_toggle"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/username_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_username"
android:inputType="textShortMessage"
android:textCursorDrawable="@null" />
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_password"
android:inputType="textPassword"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="password_toggle"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/password_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_password"
android:inputType="textPassword"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_url"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/low_security_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:text="@string/server_signup_dialog_action_low_security" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/server_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/server_signup_dialog_hint_url"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<CheckBox
android:id="@+id/low_security_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:text="@string/server_signup_dialog_action_low_security" />
</LinearLayout>

View File

@@ -58,6 +58,20 @@
app:icon="@drawable/ic_sort_list"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ProgressBar
android:id="@+id/album_list_progress_loader"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.appbar.AppBarLayout>

View File

@@ -48,18 +48,33 @@
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:singleLine="false"
android:text="@string/label_placeholder"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
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"
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"
android:foreground="?android:attr/selectableItemBackgroundBorderless">
<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"
@@ -88,6 +103,62 @@
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"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_release_year_label"
android:paddingTop="12dp"
android:paddingBottom="8dp"
android:visibility="gone"
android:animateLayoutChanges="true">
<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"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
<TextView
android:id="@+id/album_song_count_duration_textview"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingVertical="2dp"
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_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:text="@string/label_placeholder"
android:justificationMode="inter_word"
android:textAlignment="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_song_count_duration_textview" />
</androidx.constraintlayout.widget.ConstraintLayout>
<View
android:id="@+id/upper_button_divider"
style="@style/Divider"
@@ -96,7 +167,7 @@
android:layout_marginEnd="18dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_release_year_label" />
app:layout_constraintTop_toBottomOf="@+id/album_detail_view" />
<LinearLayout
android:id="@+id/album_page_button_layout"

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -110,7 +111,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="22dp">
android:paddingBottom="22dp"
android:visibility="gone"
tools:visibility="visible">>
<LinearLayout
android:layout_width="match_parent"
@@ -146,18 +149,15 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/artist_page_bio_placeholder"
layout="@layout/item_placehoder_biography"
android:visibility="gone" />
<!-- Label and button -->
<LinearLayout
android:id="@+id/artist_page_top_songs_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="22dp">
android:paddingBottom="22dp"
android:visibility="gone"
tools:visibility="visible">>
<LinearLayout
android:layout_width="match_parent"
@@ -193,55 +193,24 @@
android:paddingTop="8dp" />
</LinearLayout>
<include
android:id="@+id/artist_page_top_tracks_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<LinearLayout
android:id="@+id/artist_page_albums_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<LinearLayout
<TextView
style="@style/TitleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
style="@style/TitleLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="16dp"
android:paddingEnd="8dp"
android:text="@string/artist_page_title_album_section" />
<TextView
android:id="@+id/artist_page_albums_switch_layout_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingEnd="16dp"
android:text="@string/artist_page_switch_layout_button" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/albums_horizontal_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp"
android:visibility="visible"/>
android:text="@string/artist_page_title_album_section" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/albums_vertical_recycler_view"
android:id="@+id/albums_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
@@ -250,20 +219,16 @@
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp"
android:visibility="gone"/>
android:paddingBottom="8dp"/>
</LinearLayout>
<include
android:id="@+id/artist_page_album_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<LinearLayout
android:id="@+id/similar_artist_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
style="@style/TitleLarge"
@@ -283,16 +248,10 @@
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingEnd="16dp"
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/artist_page_similar_artist_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">
@@ -68,7 +69,9 @@
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="16dp"
android:paddingBottom="@dimen/global_padding_bottom">
android:paddingBottom="@dimen/global_padding_bottom"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/downloaded_text_view_refreshable"
@@ -76,11 +79,20 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/download_title_section"
android:layout_marginBottom="16dp"
app:layout_constraintEnd_toStartOf="@+id/downloaded_go_back_image_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/shuffle_downloaded_text_view_clickable"
style="@style/TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/download_shuffle_all_subtitle"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/downloaded_text_view_refreshable"/>
<ImageView
android:id="@+id/downloaded_go_back_image_view"
android:layout_width="24dp"
@@ -108,20 +120,12 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
android:nestedScrollingEnabled="false"
android:paddingTop="8dp"
android:paddingTop="12dp"
android:paddingBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/downloaded_text_view_refreshable" />
<include
android:id="@+id/download_downloaded_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/downloaded_text_view_refreshable" />
app:layout_constraintTop_toBottomOf="@id/shuffle_downloaded_text_view_clickable" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.core.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.core.widget.NestedScrollView 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:id="@+id/fragment_home_nested_scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -29,8 +29,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingVertical="12dp"
android:paddingHorizontal="20dp">
android:paddingHorizontal="20dp"
android:paddingVertical="12dp">
<!-- Title, secondary and supporting text -->
<TextView
@@ -111,17 +111,19 @@
android:id="@+id/home_discover_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="8dp"
android:paddingStart="8dp"
android:paddingTop="16dp"
android:paddingEnd="8dp">
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/discovery_text_view_refreshable"
@@ -153,17 +155,14 @@
android:paddingBottom="16dp" />
</LinearLayout>
<include
android:id="@+id/home_discovery_placeholder"
layout="@layout/item_placehoder_discovery"
android:visibility="gone" />
<!-- Similar tracks -->
<LinearLayout
android:id="@+id/home_similar_tracks_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/similar_tracks_pre_text_view"
@@ -171,7 +170,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_subtitle_made_for_you"
android:textAllCaps="true" />
@@ -198,17 +197,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_similar_tracks_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Best of -->
<LinearLayout
android:id="@+id/home_best_of_artist_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/most_streamed_song_pre_text_view"
@@ -216,7 +212,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_subtitle_best_of"
android:textAllCaps="true" />
@@ -243,17 +239,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_best_of_artist_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Radio Artist -->
<LinearLayout
android:id="@+id/home_radio_artist_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/radio_artist_text_view_refreshable"
@@ -261,7 +254,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_radio_station" />
@@ -278,18 +271,15 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_radio_artist_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<View
android:id="@+id/after_radio_artist_divider"
style="@style/Divider"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp" />
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
<!-- Grid tracks -->
<LinearLayout
@@ -297,7 +287,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/grid_tracks_pre_text_view"
@@ -305,7 +296,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="12dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_last_week"
android:textAllCaps="true" />
@@ -336,16 +327,20 @@
android:id="@+id/after_grid_divider"
style="@style/Divider"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp" />
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
<!-- Favorites -->
<LinearLayout
android:id="@+id/starred_tracks_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -353,8 +348,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/starred_tracks_text_view_refreshable"
@@ -387,16 +383,13 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/starred_tracks_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<LinearLayout
android:id="@+id/starred_albums_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -404,8 +397,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/starred_albums_text_view_refreshable"
@@ -414,7 +408,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_starred_albums" />
@@ -424,7 +417,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_starred_albums_see_all_button" />
</LinearLayout>
@@ -440,16 +432,13 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/starred_albums_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<LinearLayout
android:id="@+id/starred_artists_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -457,8 +446,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/starred_artists_text_view_refreshable"
@@ -467,7 +457,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_starred_artists" />
@@ -477,7 +466,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_starred_artists_see_all_button" />
</LinearLayout>
@@ -493,25 +481,23 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/starred_artists_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<View
android:id="@+id/after_favorites_divider"
style="@style/Divider"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp" />
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/home_new_releases_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:visibility="gone"
tools:visibility="visible">
<!-- New releases -->
<TextView
@@ -519,7 +505,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="8dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_new_releases" />
@@ -534,17 +520,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_new_releases_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<!-- Fashback -->
<LinearLayout
android:id="@+id/home_flashback_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<TextView
@@ -552,7 +535,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingTop="20dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_flashback" />
@@ -569,17 +552,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_flashback_placeholder"
layout="@layout/item_placeholder_year"
android:visibility="gone" />
<View
style="@style/Divider"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="12dp" />
android:layout_marginBottom="8dp"
android:visibility="gone"
tools:visibility="visible" />
<!-- Most played albums -->
<LinearLayout
@@ -587,7 +567,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -595,8 +576,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/most_played_albums_text_view_refreshable"
@@ -605,7 +587,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_most_played" />
@@ -615,7 +596,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_most_played_see_all_button" />
</LinearLayout>
@@ -633,17 +613,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_most_played_albums_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Recently played albums -->
<LinearLayout
android:id="@+id/home_recently_played_albums_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -651,8 +628,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/recently_played_albums_text_view_refreshable"
@@ -661,7 +639,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_last_played" />
@@ -672,7 +649,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_last_played_see_all_button" />
@@ -691,17 +667,14 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_recently_played_albums_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Recently added albums -->
<LinearLayout
android:id="@+id/home_recently_added_albums_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<!-- Label and button -->
<LinearLayout
@@ -709,8 +682,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/recently_added_albums_text_view_refreshable"
@@ -719,7 +693,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_recently_added" />
@@ -730,7 +703,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/home_title_recently_added_see_all_button" />
@@ -749,26 +721,22 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/home_recently_added_albums_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Shares -->
<LinearLayout
android:id="@+id/shares_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/shares_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_shares" />
@@ -783,10 +751,15 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/shares_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
<Button
android:id="@+id/home_sector_rearrangement_button"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/home_option_reorganize"
android:visibility="gone"
tools:visibility="visible"/>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
@@ -24,7 +25,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
android:id="@+id/podcast_channels_pre_text_view"
@@ -84,7 +87,9 @@
android:id="@+id/home_newest_podcasts_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible">
<TextView
style="@style/TitleLarge"
@@ -106,12 +111,6 @@
android:nestedScrollingEnabled="false"
android:paddingTop="8dp" />
</LinearLayout>
<include
android:id="@+id/podcast_episodes_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">
@@ -29,7 +30,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:paddingBottom="8dp"
android:visibility="gone"
tools:visibility="visible">
<TextView
style="@style/TitleLarge"
@@ -49,23 +52,19 @@
android:nestedScrollingEnabled="false"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingTop="4dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="4dp" />
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/library_music_folder_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Album -->
<LinearLayout
android:id="@+id/library_album_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:visibility="gone"
tools:visibility="visible">
<!-- Album -->
<!-- Label and button -->
@@ -74,8 +73,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/album_catalogue_sample_text_view_refreshable"
@@ -84,7 +84,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_album" />
@@ -94,7 +93,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:text="@string/library_title_album_see_all_button" />
@@ -113,17 +111,13 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/library_album_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<LinearLayout
android:id="@+id/library_artist_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:visibility="gone"
tools:visibility="visible">
<!-- Artist -->
<!-- Label and button -->
@@ -132,8 +126,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/artist_catalogue_sample_text_view_refreshable"
@@ -142,7 +137,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_artist" />
@@ -153,7 +147,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_artist_see_all_button" />
@@ -172,17 +165,13 @@
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/library_artist_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<LinearLayout
android:id="@+id/library_genres_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:visibility="gone"
tools:visibility="visible">
<!-- Genre -->
<!-- Label and button -->
@@ -191,8 +180,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/genre_catalogue_sample_text_view_refreshable"
@@ -201,7 +191,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_genre" />
@@ -212,7 +201,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_genre_see_all_button" />
</LinearLayout>
@@ -229,17 +217,13 @@
android:paddingEnd="8dp" />
</LinearLayout>
<include
android:id="@+id/library_genre_placeholder"
layout="@layout/item_placeholder_genre"
android:visibility="gone" />
<LinearLayout
android:id="@+id/library_playlist_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="8dp">
android:visibility="gone"
tools:visibility="visible">
<!-- Playlist -->
<!-- Label and button -->
@@ -248,8 +232,9 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<TextView
android:id="@+id/playlist_catalogue_sample_text_view_refreshable"
@@ -258,7 +243,6 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_playlist" />
@@ -268,7 +252,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="8dp"
android:paddingTop="12dp"
android:paddingEnd="8dp"
android:text="@string/library_title_playlist_see_all_button" />
@@ -285,11 +268,6 @@
android:paddingTop="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/library_playlist_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@@ -32,6 +32,7 @@
app:layout_constraintTop_toBottomOf="@+id/empty_description_image_view" />
<androidx.core.widget.NestedScrollView
android:id="@+id/now_playing_song_lyrics_sroll_view"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
@@ -49,4 +50,32 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.core.widget.NestedScrollView>
<Button
android:id="@+id/sync_lyrics_tap_button"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_margin="16dp"
android:insetLeft="0dp"
android:insetTop="0dp"
android:insetRight="0dp"
android:insetBottom="0dp"
app:cornerRadius="64dp"
android:alpha="0.7"
android:visibility="visible"
app:icon="@drawable/ic_lyrics_sync_lock"
app:layout_constraintEnd_toEndOf="@+id/now_playing_song_lyrics_sroll_view"
app:layout_constraintBottom_toBottomOf="@+id/now_playing_song_lyrics_sroll_view" />
<TextView
android:id="@+id/temp_lyrics_line_text_view"
style="@style/BodyLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:visibility="invisible"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -73,7 +73,8 @@
android:id="@+id/downloaded_item_more_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:paddingVertical="12dp"
android:paddingStart="12dp"
app:layout_constraintBottom_toBottomOf="@+id/item_cover_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/item_cover_image_view" >

View File

@@ -0,0 +1,26 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clipChildren="false"
android:orientation="horizontal">
<CheckBox
android:id="@+id/home_sector_title_check_box"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/home_sector_rearranger_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_drag_handle"
app:layout_constraintBottom_toBottomOf="@+id/home_sector_title_check_box"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/home_sector_title_check_box" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,5 +1,6 @@
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:background="?attr/selectableItemBackground"
@@ -58,7 +59,7 @@
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toTopOf="@+id/search_result_song_subtitle_text_view"
app:layout_constraintEnd_toStartOf="@+id/search_result_dowanload_indicator_image_view"
app:layout_constraintEnd_toStartOf="@+id/rating_indicator_image_view"
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
app:layout_constraintTop_toTopOf="@+id/song_cover_image_view"
app:layout_constraintVertical_chainStyle="packed" />
@@ -73,20 +74,85 @@
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="@+id/song_cover_image_view"
app:layout_constraintEnd_toStartOf="@+id/search_result_dowanload_indicator_image_view"
app:layout_constraintEnd_toStartOf="@+id/rating_indicator_image_view"
app:layout_constraintStart_toEndOf="@+id/cover_image_separator"
app:layout_constraintTop_toBottomOf="@+id/search_result_song_title_text_view" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/rating_indicator_image_view"
android:layout_width="42dp"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
app:layout_constraintBottom_toBottomOf="@+id/song_cover_image_view"
app:layout_constraintEnd_toStartOf="@+id/search_result_download_indicator_image_view"
app:layout_constraintStart_toEndOf="@+id/search_result_song_title_text_view"
app:layout_constraintTop_toTopOf="@+id/song_cover_image_view">
<ImageView
android:id="@+id/preferred_icon"
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/ic_favorite"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/rating_bar_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/preferred_icon">
<ImageView
android:id="@+id/one_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/two_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/three_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/four_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/five_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/search_result_dowanload_indicator_image_view"
android:id="@+id/search_result_download_indicator_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/song_cover_image_view"
app:layout_constraintEnd_toStartOf="@+id/search_result_song_more_button"
app:layout_constraintStart_toEndOf="@+id/search_result_song_title_text_view"
app:layout_constraintTop_toTopOf="@+id/song_cover_image_view">
app:layout_constraintStart_toEndOf="@+id/rating_indicator_image_view"
app:layout_constraintTop_toTopOf="@+id/song_cover_image_view"
tools:visibility="visible">
<ImageView
android:layout_width="18dp"
@@ -102,7 +168,7 @@
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="@+id/song_cover_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/search_result_dowanload_indicator_image_view"
app:layout_constraintStart_toEndOf="@+id/search_result_download_indicator_image_view"
app:layout_constraintTop_toTopOf="@+id/song_cover_image_view">
<ImageView

View File

@@ -31,10 +31,16 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingBottom="16dp"
android:singleLine="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/album_name_label" />
<View
android:layout_width="match_parent"
android:layout_height="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/artist_name_label"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="9"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="?attr/colorSurface" />
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="156dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
</LinearLayout>

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="9"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:background="?attr/colorSurface" />
</LinearLayout>
<ImageView
android:layout_width="match_parent"
android:layout_height="190dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
</LinearLayout>

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="8"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="12dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="8dp"
android:layout_weight="2"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="8dp">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -1,110 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="128dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="196dp"
android:layout_height="54dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
android:layout_marginBottom="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,294 +0,0 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp"
android:paddingBottom="8dp">
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="8"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="12dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="8dp"
android:layout_weight="2"
android:background="?attr/colorSurface" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingTop="8dp">
<ImageView
android:id="@+id/album_cover_placeholder_1"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_title_placeholder_1"
android:layout_width="172dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_1"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="124dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_1"
app:layout_constraintTop_toBottomOf="@+id/album_title_placeholder_1" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/album_cover_placeholder_2"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_title_placeholder_2"
android:layout_width="172dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_2"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="124dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_2"
app:layout_constraintTop_toBottomOf="@+id/album_title_placeholder_2" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/album_cover_placeholder_3"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_title_placeholder_3"
android:layout_width="172dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_3"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="124dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_3"
app:layout_constraintTop_toBottomOf="@+id/album_title_placeholder_3" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/album_cover_placeholder_4"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_title_placeholder_4"
android:layout_width="172dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_4"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="124dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_4"
app:layout_constraintTop_toBottomOf="@+id/album_title_placeholder_4" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingStart="16dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
<ImageView
android:id="@+id/album_cover_placeholder_5"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:background="?attr/colorSurface"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/album_title_placeholder_5"
android:layout_width="172dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_5"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="124dp"
android:layout_height="12dp"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:background="?attr/colorSurface"
app:layout_constraintStart_toEndOf="@+id/album_cover_placeholder_5"
app:layout_constraintTop_toBottomOf="@+id/album_title_placeholder_5" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_marginStart="12dp"
android:layout_marginEnd="12dp"
android:background="?attr/colorSurface"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="8dp"
android:paddingTop="8dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="12dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="8dp"
android:layout_weight="8"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="12dp"
android:layout_height="14dp"
android:layout_marginStart="8dp"
android:layout_marginTop="22dp"
android:layout_marginEnd="8dp"
android:layout_weight="2"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingEnd="8dp">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingEnd="8dp">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="172dp"
android:layout_height="172dp"
android:layout_gravity="center"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="124dp"
android:layout_height="14dp"
android:layout_marginTop="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="112dp"
android:layout_height="12dp"
android:layout_marginTop="4dp"
android:background="?attr/colorSurface" />
</LinearLayout>
</LinearLayout>
</LinearLayout>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/placeholder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="128dp"
android:layout_height="24dp"
android:layout_marginStart="16dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="horizontal">
<ImageView
android:layout_width="172dp"
android:layout_height="72dp"
android:layout_gravity="center"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="172dp"
android:layout_height="72dp"
android:layout_gravity="center"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
<ImageView
android:layout_width="172dp"
android:layout_height="72dp"
android:layout_gravity="center"
android:layout_marginEnd="12dp"
android:layout_marginBottom="8dp"
android:background="?attr/colorSurface" />
</LinearLayout>
</LinearLayout>

View File

@@ -1,11 +1,12 @@
<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content"
android:clipChildren="false"
android:foreground="?attr/selectableItemBackground"
android:orientation="horizontal"
android:paddingHorizontal="24dp"
android:paddingHorizontal="16dp"
android:paddingTop="3dp"
android:paddingBottom="3dp">
@@ -30,9 +31,11 @@
android:paddingEnd="12dp"
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"
app:layout_constraintEnd_toStartOf="@+id/rating_indicator_image_view"
app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintTop_toTopOf="@id/queue_song_cover_image_view"
app:layout_constraintBottom_toTopOf="@id/queue_song_subtitle_text_view"
app:layout_constraintVertical_chainStyle="packed"/>
<TextView
android:id="@+id/queue_song_subtitle_text_view"
@@ -45,19 +48,84 @@
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constrainedWidth="true"
app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"
app:layout_constraintEnd_toEndOf="@+id/queue_song_title_text_view"
app:layout_constraintStart_toEndOf="@+id/queue_song_cover_image_view"
app:layout_constraintTop_toBottomOf="@+id/queue_song_title_text_view" />
app:layout_constraintTop_toBottomOf="@+id/queue_song_title_text_view"
app:layout_constraintBottom_toBottomOf="@+id/queue_song_cover_image_view"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/rating_indicator_image_view"
android:layout_width="42dp"
android:layout_height="wrap_content"
android:paddingVertical="8dp"
android:layout_marginHorizontal="12dp"
app:layout_constraintBottom_toBottomOf="@+id/queue_song_cover_image_view"
app:layout_constraintEnd_toStartOf="@+id/queue_song_holder_image"
app:layout_constraintStart_toEndOf="@+id/queue_song_title_text_view"
app:layout_constraintTop_toTopOf="@+id/queue_song_cover_image_view">
<ImageView
android:id="@+id/preferred_icon"
android:layout_width="18dp"
android:layout_height="18dp"
android:background="@drawable/ic_favorite"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<LinearLayout
android:id="@+id/rating_bar_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/preferred_icon">
<ImageView
android:id="@+id/one_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/two_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/three_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/four_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
<ImageView
android:id="@+id/five_star_icon"
android:layout_width="8dp"
android:layout_height="8dp"
tools:src="@drawable/ic_star" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<ImageView
android:id="@+id/queue_song_holder_image"
android:layout_width="36dp"
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:src="@drawable/ic_drag_handle"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="@+id/queue_song_cover_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
app:layout_constraintStart_toEndOf="@+id/rating_indicator_image_view"
app:layout_constraintTop_toTopOf="@+id/queue_song_cover_image_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

Some files were not shown because too many files have changed in this diff Show More