108 Commits
3.5.2 ... 3.5.8

Author SHA1 Message Date
antonio
dfc246d079 feat: increased buffering multiplier limits 2023-11-26 19:14:42 +01:00
antonio
0a5983e32b feat: implemented streaming buffering strategy 2023-11-26 19:10:21 +01:00
antonio
9fde629233 fix: network security configuration with cleartext traffic opt-out 2023-11-26 17:36:38 +01:00
antonio
e089c5e466 gradle: bump up code version 2023-11-26 17:27:55 +01:00
antonio
94b0572958 feat: network security configuration 2023-11-26 17:22:38 +01:00
antonio
133b5e4794 feat: implemented settings for enabling or disabling music scrobbling 2023-11-26 16:39:05 +01:00
antonio
6b0ba573de feat: improved live transcoding UI 2023-11-26 16:20:07 +01:00
antonio
6ae7fc2172 gradle: dependencies update 2023-11-26 16:18:47 +01:00
antonio
9cabfb0e2c style: PR code cleanup 2023-11-05 17:10:13 +01:00
antonio
17f05cb3d8 Merge remote-tracking branch 'origin/main' 2023-11-05 16:44:23 +01:00
CappielloAntonio
f2839f4ff9 Merge pull request #100 from kingbluezback/main
style: updated dialog graphics to comply with Material You guidelines
2023-11-05 16:44:05 +01:00
antonio
9aadcb91fb fix: null checking 2023-11-05 16:43:13 +01:00
antonio
5d4dfe1ac7 gradle: gradle and dependencies update 2023-11-05 16:07:12 +01:00
kingbluezback
d3d76dd435 Change Dialogs
Update the dialogs to use material you theme.
2023-10-22 20:42:40 -07:00
kingbluezback
0c3086d68b Update activity_main.xml
Update the apps background color for material you.
2023-10-22 20:35:41 -07:00
kingbluezback
9b3b9771e6 Update strings.xml
When editing a playlist the title is "create playlist" instead of "edit playlist"
2023-10-22 20:33:51 -07:00
antonio
a92403bda4 style: sort translation strings 2023-10-14 16:09:19 +02:00
antonio
67905affd3 feat: added podcast description to the right page of the player 2023-10-12 22:57:38 +02:00
antonio
c20cadff94 clean: cleaned up the podcast listening UI 2023-10-12 22:36:48 +02:00
antonio
0cc45b2b12 clean: code cleanup 2023-10-12 22:36:03 +02:00
antonio
b6063f6b20 fix: forced media type during playback 2023-10-12 22:35:34 +02:00
antonio
678c61e569 clean: removed unused properties of podcast episodes 2023-10-12 22:34:48 +02:00
antonio
e88d6d7844 fix: music player now stays closed on startup if there are no songs in the queue 2023-10-12 21:31:46 +02:00
antonio
bae43cbfe9 gradle: dependencies update 2023-10-12 21:11:24 +02:00
antonio
68c77cefff feat: added songBottomSheet to track files within directories 2023-10-11 22:49:26 +02:00
antonio
75980ee18b feat: show download button only if there are music files in the current directory fragment 2023-10-11 22:20:50 +02:00
antonio
ed30198c8d feat: implemented playlist deletion starting from current track 2023-10-11 21:48:04 +02:00
antonio
185a671f82 fix: resolved an issue with podcast cover image display 2023-10-11 20:55:33 +02:00
antonio
42905819a7 Merge remote-tracking branch 'origin/main' 2023-10-07 16:01:40 +02:00
CappielloAntonio
93d6faa392 github: update crash-report.md 2023-10-07 15:52:28 +02:00
CappielloAntonio
64337775d6 Merge pull request #97 from rkowalsk/main
feat: added french localization
2023-10-07 15:47:29 +02:00
Romain Kowalski
d690cf37fb fix: shortened Wifi transcoding string that was overflowing 2023-10-05 16:56:14 +02:00
Romain Kowalski
32c8ef3b25 Add French localization 2023-10-05 16:23:17 +02:00
antonio
607c68967d gradle: dependencies update 2023-09-29 11:11:30 +02:00
antonio
7fb9f63d1f Merge remote-tracking branch 'origin/main' 2023-09-17 22:33:39 +02:00
antonio
a04a2072f7 feat: implemented a second grid layout for album display on the artist page 2023-09-17 22:33:25 +02:00
antonio
72bd71dea2 fix: error handling 2023-09-17 22:04:07 +02:00
CappielloAntonio
f87328f874 Merge pull request #84 from GallowsDove/templates
style: use proper markdown for issue templates
2023-09-17 21:16:54 +02:00
GallowsDove
4d9b71d48e fix: fix indent in templates 2023-09-17 20:02:43 +02:00
GallowsDove
06f8bc771a feat: Use markdown for github templates 2023-09-17 19:59:27 +02:00
antonio
24747207b8 gradle: bump up code version 2023-09-17 19:15:58 +02:00
antonio
e762c91ff3 feat: added setting to request estimated duration of transcoded file 2023-09-17 19:12:45 +02:00
antonio
0fe4636fe1 feat: parameterized track sharing feature 2023-09-17 18:51:22 +02:00
antonio
8ab011781e fix: made expiration date TextInputEditText unselectable 2023-09-17 18:27:16 +02:00
antonio
7f820bd5a6 feat: implemented playlist sharing 2023-09-17 18:22:57 +02:00
antonio
26b8f3f65c feat: implemented album and song sharing 2023-09-17 16:46:21 +02:00
antonio
f172a00fb7 feat: implemented updating shared items through dialog 2023-09-17 16:43:09 +02:00
antonio
077d22167c feat: added bottomSheet for shared items on the home screen 2023-09-17 16:42:07 +02:00
antonio
ebd1582d1c feat: added shared items list on the homepage 2023-09-17 16:40:41 +02:00
antonio
5e486d4794 feat: implemented client, service, and repository for sharing APIs 2023-09-17 16:38:08 +02:00
antonio
537d0c6d8f gradle: bump up code version 2023-09-15 22:13:13 +02:00
antonio
4fc75f5c46 refactor: code cleanup 2023-09-15 22:02:31 +02:00
CappielloAntonio
baf1bc48b0 Merge pull request #82 from GallowsDove/notifications
feat: add notification groups
2023-09-15 21:50:08 +02:00
antonio
f26a123646 style: added icon for indexes 2023-09-15 21:42:24 +02:00
antonio
7160e3f4b9 style: modified placeholder icon background colors and added folder icon 2023-09-15 21:34:44 +02:00
antonio
24b3161a8e style: hide top-listened until server response is confirmed 2023-09-15 21:33:39 +02:00
GallowsDove
383fbd1c49 feat: add notification groups 2023-09-15 13:09:48 +02:00
antonio
c0f5abdfae Merge remote-tracking branch 'origin/main' 2023-09-13 17:27:49 +02:00
antonio
a50e50d797 fix: set no replay gain if array is null or empty 2023-09-13 17:27:43 +02:00
CappielloAntonio
1f9dac9e3a github: add crash issue template 2023-09-13 17:01:51 +02:00
CappielloAntonio
f57ac6d624 github: add feature request issue template 2023-09-13 16:56:09 +02:00
CappielloAntonio
0b96fe5605 github: add bug issue template 2023-09-13 16:51:05 +02:00
antonio
16561be854 fix: null checking 2023-09-13 16:19:50 +02:00
antonio
570ad1c984 gradle: dependencies update 2023-09-13 16:09:02 +02:00
antonio
736bcdb994 style: uniformed button size for "more buttons" to make them consistent and easier to click 2023-09-09 18:43:18 +02:00
antonio
e71472f498 style: pull request refactoring 2023-09-08 15:56:50 +02:00
CappielloAntonio
fb328f26b8 Merge pull request #70 from ivan-avalos/main
feat: improved item placeholders
2023-09-08 15:38:17 +02:00
antonio
c26aba8b2d style: pull request refactoring 2023-09-08 15:30:05 +02:00
antonio
a17de1de8d fix: null checking 2023-09-08 14:41:32 +02:00
antonio
7ea159cb10 Merge remote-tracking branch 'origin/main' 2023-09-08 14:20:15 +02:00
CappielloAntonio
8ed98bbedc Merge pull request #61 from GallowsDove/main
feat: add bottom sheet for grouped views in download tab
2023-09-08 14:17:49 +02:00
antonio
43a1b5b93a fix: null checking 2023-09-08 14:12:35 +02:00
antonio
977754f02c clean: code cleanup 2023-09-08 10:54:43 +02:00
antonio
10aae5fa15 test: implemented and temporarily set aside the implementation of a customLoadController 2023-09-08 10:52:21 +02:00
antonio
dee60e5e8f fix: fixed application logic of Replay Gain 2023-09-08 10:51:09 +02:00
Iván Ávalos
0c05b77849 feat: improved item placeholders 2023-09-07 19:48:03 -06:00
GallowsDove
223954e9ca Merge branch 'main' into main 2023-09-07 10:39:03 +02:00
antonio
fdc8c50d25 clean: code cleanup 2023-09-06 22:56:54 +02:00
antonio
aa7872d030 fix: fixed lag when opening AlbumBottomSheet caused by disappearing download removal button 2023-09-06 22:33:20 +02:00
antonio
9ed7744421 fix: updated download section navigation to match the system's correct stack-based navigation 2023-09-06 21:49:23 +02:00
antonio
c19db362d9 gradle: bump up code version 2023-09-06 18:31:04 +02:00
antonio
6a505eea4e feat: implemented a dialog box for displaying details of the currently playing track 2023-09-06 18:28:32 +02:00
antonio
99be2764d0 feat: added the ability to shuffle the playback queue 2023-09-06 15:03:28 +02:00
antonio
b656ad9e7f feat: increased visibility of gestures within the music player 2023-09-06 15:02:32 +02:00
GallowsDove
75e5756d7e fix: Fix performance issues with DownloadedBottomSheet with large number of grouped entries 2023-09-06 00:53:59 +02:00
antonio
13d8bbf877 feat: made tap on the track cover more prominent to discover quick action commands 2023-09-05 21:33:31 +02:00
GallowsDove
f43d7fb394 feat: Add bottom sheet for grouped views in Download tab 2023-09-05 18:54:48 +02:00
antonio
68527b2c91 style: adjusted padding of elements for improved consistency 2023-08-31 16:21:36 +02:00
antonio
347c074368 style: fixed visibility state of playback icons 2023-08-31 16:20:55 +02:00
antonio
2a74f51f8d fix: corrected folder navigation, now displaying correct subfolders 2023-08-31 16:20:05 +02:00
antonio
1a75369591 fix: null checking 2023-08-31 15:58:00 +02:00
antonio
8f6e775ca9 gradle: bump up code version 2023-08-30 17:53:44 +02:00
antonio
bad0fa6c23 fix: fixed a bug in the paginated song lists 2023-08-30 17:52:44 +02:00
antonio
d4caa6f209 build: dependencies update 2023-08-29 08:03:47 +02:00
antonio
723bdf9771 clean: code cleanup 2023-08-28 12:26:04 +02:00
antonio
49fbd85bb4 clean: code cleanup 2023-08-28 12:25:36 +02:00
CappielloAntonio
f040fbf0cf Merge pull request #50 from GallowsDove/main
feat: show albums instead of the artist in artist's top songs
2023-08-28 12:17:54 +02:00
antonio
efb2213ab7 fix: files without embedded cover art now display the image in the music player notification 2023-08-28 12:15:40 +02:00
GallowsDove
742ac6b17d feat: show albums instead of the artist in artist's top songs. 2023-08-28 11:28:59 +02:00
antonio
ae7761cb96 fix: null checking 2023-08-28 10:51:16 +02:00
antonio
c977982d64 gradle: bump up code version 2023-08-25 15:38:05 +02:00
antonio
28fc3dca36 fix: set initial visibility of the grid to "Gone" and make it appear once the elements are successfully loaded 2023-08-25 15:36:55 +02:00
antonio
f1cf65a371 fix: redefined proguard rules to make Retrofit work for everyone 2023-08-25 15:28:47 +02:00
antonio
beb1d29e8f fix: delayed grid display until server connectivity is confirmed 2023-08-25 15:27:39 +02:00
antonio
1eda7cef9e fix: fix a crash when clicking the dot menu of an empty playlist 2023-08-25 12:32:24 +02:00
antonio
1af92ad949 fix: now refreshing playlist view every time library is visited 2023-08-25 12:23:26 +02:00
antonio
3fc9b35fe4 fix: a callback on playlist editor dialog closing tells me when to refresh the playlist view 2023-08-25 12:22:43 +02:00
antonio
56b48dbd4d fix: fixed race condition issue in playlist update 2023-08-25 12:20:49 +02:00
157 changed files with 4465 additions and 890 deletions

35
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@@ -0,0 +1,35 @@
---
name: Bug report
about: Create a report to help us improve
title: "[BUG] - "
labels: ''
assignees: ''
---
## Describe the bug
<!-- A clear and concise description of what the bug is. -->
## Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
## To Reproduce
<!--
Outline the steps required to reproduce the bug, including any specific actions, inputs, or conditions:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
## Environment
- Android device: [Device Model]
- Android OS version: [Android Version]
- App version: [App Version]
- Other relevant details: [e.g., specific network conditions, external dependencies]
## Logs or Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
## Additional context
<!-- Add any other context about the problem here. -->

41
.github/ISSUE_TEMPLATE/crash-report.md vendored Normal file
View File

@@ -0,0 +1,41 @@
---
name: Crash report
about: Tell us how to replicate the crash
title: "[CRASH] - "
labels: ''
assignees: ''
---
## Description
<!-- Provide a clear and concise description of the crash you encountered. -->
## Steps to Reproduce
<!--
Please provide the steps to reproduce the crash:
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See the crash
-->
## Environment
- Android device: [Device Model]
- Android OS version: [Android Version]
- App version: [App Version]
- Other relevant details: [e.g., specific network conditions, external dependencies]
## Crash Logs/Stack trace
<!-- If available, please provide the crash log or stack trace related to the crash. Include it inside a code block (surround with triple backticks ```). Please use the unsigned apk (app-tempo-debug.apk), as the logs would be illegible and therefore useless for this purpose. -->
## Screenshots
<!-- If applicable, add screenshots to help explain the problem. -->
## Additional Context
<!-- Add any other context about the problem here. -->
## Reproducibility
<!-- Mention the frequency of the crash occurrence (e.g., always, sometimes, occasionally). -->
## Additional Notes
<!-- Include any other notes or details that could be helpful for troubleshooting the crash. -->

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature Request] - "
labels: ''
assignees: ''
---
## Summary
<!-- Provide a concise summary of the feature you are requesting. -->
## Description
<!-- Please describe in detail the feature you would like to see implemented. -->
## Use Case
<!-- Explain why this feature is important and how it would improve the user experience. -->
## Additional context
<!-- Include any additional information, screenshots, or examples that could be helpful in understanding or implementing the feature. -->

View File

@@ -3,8 +3,8 @@ apply plugin: 'kotlin-android'
apply plugin: 'kotlin-parcelize'
android {
compileSdk 34
buildToolsVersion "34.0.0"
compileSdk = 34
buildToolsVersion = "34.0.0"
defaultConfig {
minSdkVersion 24
@@ -22,18 +22,18 @@ android {
}
}
flavorDimensions "default"
flavorDimensions += "default"
productFlavors {
tempo {
dimension "default"
dimension = "default"
applicationId 'com.cappielloantonio.tempo'
versionCode 17
versionName '3.5.2'
versionCode 22
versionName '3.5.8'
}
notquitemy {
dimension "default"
dimension = "default"
applicationId "com.cappielloantonio.notquitemy.tempo"
versionCode 1
versionName "1.0.0"
@@ -72,29 +72,29 @@ dependencies {
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.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.0'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.room:room-runtime:2.5.2'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.5'
implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.room:room-runtime:2.6.0'
implementation 'androidx.core:core-splashscreen:1.0.1'
implementation "androidx.appcompat:appcompat:1.6.1"
// Android Material
implementation 'com.google.android.material:material:1.9.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.1.1'
implementation 'androidx.media3:media3-common:1.1.1'
implementation 'androidx.media3:media3-exoplayer:1.1.1'
implementation 'androidx.media3:media3-ui:1.1.1'
tempoImplementation 'androidx.media3:media3-cast:1.1.1'
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'
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
annotationProcessor 'androidx.room:room-compiler:2.5.2'
annotationProcessor 'androidx.room:room-compiler:2.6.0'
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'

View File

@@ -21,4 +21,5 @@
#-renamesourcefileattribute SourceFile
-keepattributes SourceFile, LineNumberTable
-keep public class * extends java.lang.Exception
-keep public class * extends java.lang.Exception
-keep class retrofit2.** { *; }

View File

@@ -19,7 +19,8 @@
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme.SplashScreen"
android:usesCleartextTraffic="true">
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config">
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"

View File

@@ -5,6 +5,9 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.content.res.AppCompatResources;
import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestBuilder;
import com.bumptech.glide.RequestManager;
@@ -15,6 +18,7 @@ import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.signature.ObjectKey;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.util.Util;
import com.google.android.material.elevation.SurfaceColors;
@@ -28,16 +32,53 @@ public class CustomGlideRequest {
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
public static RequestOptions createRequestOptions(Context context, String item) {
public enum ResourceType {
Unknown,
Album,
Artist,
Folder,
Directory,
Playlist,
Podcast,
Radio,
Song,
}
public static RequestOptions createRequestOptions(Context context, String item, ResourceType type) {
return new RequestOptions()
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.error(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(getPlaceholder(context, type))
.error(getPlaceholder(context, type))
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(new ObjectKey(item != null ? item : 0))
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS));
}
@Nullable
private static Drawable getPlaceholder(Context context, ResourceType type) {
switch (type) {
case Album:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_album);
case Artist:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_artist);
case Folder:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_folder);
case Directory:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_directory);
case Playlist:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_playlist);
case Podcast:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_podcast);
case Radio:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_radio);
case Song:
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_song);
default:
case Unknown:
return new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context));
}
}
public static String createUrl(String item, int size) {
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
@@ -72,18 +113,18 @@ public class CustomGlideRequest {
private final RequestManager requestManager;
private Object item;
private Builder(Context context, String item) {
private Builder(Context context, String item, ResourceType type) {
this.requestManager = Glide.with(context);
if (item != null && !Preferences.isDataSavingMode()) {
this.item = createUrl(item, Preferences.getImageSize());
}
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item));
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item, type));
}
public static Builder from(Context context, String item) {
return new Builder(context, item);
public static Builder from(Context context, String item, ResourceType type) {
return new Builder(context, item, type);
}
public RequestBuilder<Drawable> build() {

View File

@@ -29,4 +29,7 @@ public interface ClickCallback {
default void onMusicFolderClick(Bundle bundle) {}
default void onMusicDirectoryClick(Bundle bundle) {}
default void onMusicIndexClick(Bundle bundle) {}
default void onDownloadGroupLongClick(Bundle bundle) {}
default void onShareClick(Bundle bundle) {}
default void onShareLongClick(Bundle bundle) {}
}

View File

@@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.interfaces;
import androidx.annotation.Keep;
@Keep
public interface PlaylistCallback {
default void onDismiss() {}
}

View File

@@ -105,7 +105,9 @@ public class AlbumRepository {
List<Child> tracks = new ArrayList<>();
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
}
}
albumTracks.setValue(tracks);
@@ -247,7 +249,7 @@ 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().getAlbumList2() != null) {
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()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else {

View File

@@ -63,9 +63,11 @@ public class ArtistRepository {
if (response.isSuccessful() && response.body() != null) {
List<ArtistID3> artists = new ArrayList<>();
if(response.body().getSubsonicResponse().getArtists() != null) {
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
artists.addAll(index.getArtists());
if(index != null && index.getArtists() != null) {
artists.addAll(index.getArtists());
}
}
}

View File

@@ -95,12 +95,29 @@ public class PlaylistRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
Log.d("PLAYLIST", response.toString());
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d("PLAYLIST", t.toString());
}
});
}
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.deletePlaylist(playlistId)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
createPlaylist(null, name, songsId);
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}

View File

@@ -59,7 +59,7 @@ public class PodcastRepository {
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure()");
}
});

View File

@@ -1,7 +1,5 @@
package com.cappielloantonio.tempo.repository;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
@@ -36,7 +34,9 @@ public class SearchingRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
}
}
@Override
@@ -57,7 +57,9 @@ public class SearchingRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
if (response.isSuccessful() && response.body() != null) {
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
}
}
@Override
@@ -80,7 +82,7 @@ public class SearchingRepository {
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
List<String> newSuggestions = new ArrayList();
if (response.isSuccessful() && response.body() != null) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
newSuggestions.add(artistID3.getName());
@@ -102,8 +104,6 @@ public class SearchingRepository {
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
Log.d("suggestionsWithoutDuplicates", suggestionsWithoutDuplicates.toString());
suggestions.setValue(suggestionsWithoutDuplicates);
}
}

View File

@@ -0,0 +1,99 @@
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.Share;
import java.util.ArrayList;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class SharingRepository {
public MutableLiveData<List<Share>> getShares() {
MutableLiveData<List<Share>> shares = new MutableLiveData<>(new ArrayList<>());
App.getSubsonicClientInstance(false)
.getSharingClient()
.getShares()
.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().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null) {
shares.setValue(response.body().getSubsonicResponse().getShares().getShares());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return shares;
}
public MutableLiveData<Share> createShare(String id, String description, Long expires) {
MutableLiveData<Share> share = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getSharingClient()
.createShare(id, description, expires)
.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().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null && response.body().getSubsonicResponse().getShares().getShares().get(0) != null) {
share.setValue(response.body().getSubsonicResponse().getShares().getShares().get(0));
} else {
share.setValue(null);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
share.setValue(null);
}
});
return share;
}
public void updateShare(String id, String description, Long expires) {
App.getSubsonicClientInstance(false)
.getSharingClient()
.updateShare(id, description, expires)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
public void deleteShare(String id) {
App.getSubsonicClientInstance(false)
.getSharingClient()
.deleteShare(id)
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
}

View File

@@ -97,7 +97,7 @@ public class SongRepository {
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
}
});
@@ -205,7 +205,7 @@ public class SongRepository {
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
}
});

View File

@@ -51,17 +51,40 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
}
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
private static final String TAG = "TerminalStateNotificatinHelper";
private final Context context;
private final DownloadNotificationHelper notificationHelper;
private final Notification successfulDownloadGroupNotification;
private final Notification failedDownloadGroupNotification;
private final int successfulDownloadGroupNotificationId;
private final int failedDownloadGroupNotificationId;
private int nextNotificationId;
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
this.context = context.getApplicationContext();
this.notificationHelper = notificationHelper;
nextNotificationId = firstNotificationId;
successfulDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
this.context,
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP,
R.drawable.ic_check_circle,
"Downloads completed"
);
failedDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
this.context,
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP,
R.drawable.ic_error,
"Downloads failed"
);
successfulDownloadGroupNotificationId = nextNotificationId++;
failedDownloadGroupNotificationId = nextNotificationId++;
}
@Override
@@ -70,9 +93,13 @@ public class DownloaderService extends androidx.media3.exoplayer.offline.Downloa
if (download.state == Download.STATE_COMPLETED) {
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);
} 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();
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
} else {
return;
}

View File

@@ -12,6 +12,7 @@ import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
@@ -204,6 +205,22 @@ public class MediaManager {
}
}
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
swapDatabase(media);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
@@ -238,6 +255,21 @@ public class MediaManager {
}
}
public static void removeRange(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int fromItem, int toItem) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
try {
if (mediaBrowserListenableFuture.isDone()) {
mediaBrowserListenableFuture.get().removeMediaItems(fromItem, toItem);
removeRangeDatabase(media, fromItem, toItem);
}
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
}
public static void getCurrentIndex(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, MediaIndexCallback callback) {
if (mediaBrowserListenableFuture != null) {
mediaBrowserListenableFuture.addListener(() -> {
@@ -262,7 +294,7 @@ public class MediaManager {
}
public static void scrobble(MediaItem mediaItem) {
if (mediaItem != null) {
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
}
}
@@ -304,6 +336,14 @@ public class MediaManager {
}
}
private static void removeRangeDatabase(List<Child> media, int fromItem, int toItem) {
List<Child> toRemove = media.subList(fromItem, toItem);
media.removeAll(toRemove);
getQueueRepository().insertAll(media, true, 0);
}
public static void clearDatabase() {
getQueueRepository().deleteAll();
}

View File

@@ -10,6 +10,7 @@ import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClie
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
import com.cappielloantonio.tempo.subsonic.api.sharing.SharingClient;
import com.cappielloantonio.tempo.subsonic.api.system.SystemClient;
import com.cappielloantonio.tempo.subsonic.base.Version;
@@ -33,6 +34,7 @@ public class Subsonic {
private MediaLibraryScanningClient mediaLibraryScanningClient;
private BookmarksClient bookmarksClient;
private InternetRadioClient internetRadioClient;
private SharingClient sharingClient;
public Subsonic(SubsonicPreferences preferences) {
this.preferences = preferences;
@@ -119,6 +121,13 @@ public class Subsonic {
return internetRadioClient;
}
public SharingClient getSharingClient() {
if (sharingClient == null) {
sharingClient = new SharingClient(this);
}
return sharingClient;
}
public String getUrl() {
String url = preferences.getServerUrl() + "/rest/";
return url.replace("//rest", "/rest");

View File

@@ -0,0 +1,41 @@
package com.cappielloantonio.tempo.subsonic.api.sharing;
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 SharingClient {
private static final String TAG = "BrowsingClient";
private final Subsonic subsonic;
private final SharingService sharingService;
public SharingClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.sharingService = new RetrofitClient(subsonic).getRetrofit().create(SharingService.class);
}
public Call<ApiResponse> getShares() {
Log.d(TAG, "getShares()");
return sharingService.getShares(subsonic.getParams());
}
public Call<ApiResponse> createShare(String id, String description, Long expires) {
Log.d(TAG, "createShare()");
return sharingService.createShare(subsonic.getParams(), id, description, expires);
}
public Call<ApiResponse> updateShare(String id, String description, Long expires) {
Log.d(TAG, "updateShare()");
return sharingService.updateShare(subsonic.getParams(), id, description, expires);
}
public Call<ApiResponse> deleteShare(String id) {
Log.d(TAG, "deleteShare()");
return sharingService.deleteShare(subsonic.getParams(), id);
}
}

View File

@@ -0,0 +1,24 @@
package com.cappielloantonio.tempo.subsonic.api.sharing;
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 SharingService {
@GET("getShares")
Call<ApiResponse> getShares(@QueryMap Map<String, String> params);
@GET("createShare")
Call<ApiResponse> createShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
@GET("updateShare")
Call<ApiResponse> updateShare(@QueryMap Map<String, String> params, @Query("id") String id, @Query("description") String description, @Query("expires") Long expires);
@GET("deleteShare")
Call<ApiResponse> deleteShare(@QueryMap Map<String, String> params, @Query("id") String id);
}

View File

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

View File

@@ -14,6 +14,7 @@ class PodcastChannel : Parcelable {
var url: String? = null
var title: String? = null
var description: String? = null
@SerializedName("coverArt")
var coverArtId: String? = null
var originalImageUrl: String? = null
var status: String? = null

View File

@@ -12,12 +12,10 @@ class PodcastEpisode : Parcelable {
var id: String? = null
@SerializedName("parent")
var parentId: String? = null
@SerializedName("isDir")
var isDir = false
var title: String? = null
var album: String? = null
var artist: String? = null
var track: Int? = null
var year: Int? = null
var genre: String? = null
@SerializedName("coverArt")
@@ -25,26 +23,14 @@ class PodcastEpisode : Parcelable {
var size: Long? = null
var contentType: String? = null
var suffix: String? = null
var transcodedContentType: String? = null
var transcodedSuffix: String? = null
var duration: Int? = null
@SerializedName("bitRate")
var bitrate: Int? = null
var path: String? = null
@SerializedName("isVideo")
var isVideo: Boolean = false
var userRating: Int? = null
var averageRating: Double? = null
var playCount: Long? = null
var discNumber: Int? = null
var created: Date? = null
var starred: Date? = null
var albumId: String? = null
var artistId: String? = null
var type: String? = null
var bookmarkPosition: Long? = null
var originalWidth: Int? = null
var originalHeight: Int? = null
var streamId: String? = null
var channelId: String? = null
var description: String? = null

View File

@@ -1,10 +1,15 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.*
@Keep
class Share {
@Parcelize
class Share : Parcelable {
@SerializedName("entry")
var entries: List<Child>? = null
var id: String? = null
var url: String? = null

View File

@@ -1,8 +1,10 @@
package com.cappielloantonio.tempo.subsonic.models
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
class Shares {
@SerializedName("share")
var shares: List<Share>? = null
}

View File

@@ -119,8 +119,6 @@ public class MainActivity extends BaseActivity {
fragmentManager.beginTransaction().replace(R.id.player_bottom_sheet, new PlayerBottomSheetFragment(), "PlayerBottomSheet").commit();
setBottomSheetInPeek(mainViewModel.isQueueLoaded());
collapseBottomSheet();
}
public void setBottomSheetInPeek(Boolean isVisible) {
@@ -163,7 +161,7 @@ public class MainActivity extends BaseActivity {
switch (state) {
case BottomSheetBehavior.STATE_HIDDEN:
hideMusicSession();
resetMusicSession();
break;
case BottomSheetBehavior.STATE_COLLAPSED:
if (playerBottomSheetFragment != null)

View File

@@ -42,7 +42,7 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.albumCoverImageView);
}

View File

@@ -42,7 +42,7 @@ public class AlbumArtistPageOrSimilarAdapter extends RecyclerView.Adapter<AlbumA
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.artistPageAlbumCoverImageView);
}

View File

@@ -77,7 +77,7 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.albumCatalogueCoverImageView);
}

View File

@@ -44,7 +44,7 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), album.getCoverArtId())
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.albumCoverImageView);
}

View File

@@ -47,7 +47,7 @@ public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(holder.item.artistCoverImageView);
}

View File

@@ -77,7 +77,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(holder.item.artistCatalogueCoverImageView);
}

View File

@@ -48,7 +48,7 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
}
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(holder.item.artistCoverImageView);
}

View File

@@ -41,7 +41,7 @@ public class ArtistSimilarAdapter extends RecyclerView.Adapter<ArtistSimilarAdap
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getCoverArtId())
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(holder.item.similarArtistCoverImageView);
}

View File

@@ -43,7 +43,7 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.discoverSongCoverImageView);
}

View File

@@ -163,7 +163,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
@@ -186,12 +186,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.VISIBLE);
if (position > 0 && grouped.get(position - 1) != null && !Objects.equals(grouped.get(position - 1).getArtist(), grouped.get(position).getArtist())) {
@@ -208,12 +208,12 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ARTIST, song.getArtistId(), songs)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.itemCoverImageView);
holder.item.itemCoverImageView.setVisibility(View.VISIBLE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
@@ -224,7 +224,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_GENRE, song.getGenre(), songs)));
holder.item.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
@@ -235,7 +235,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_YEAR, song.getYear().toString(), songs)));
holder.item.itemCoverImageView.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.GONE);
holder.item.downloadedItemMoreButton.setVisibility(View.VISIBLE);
holder.item.divider.setVisibility(View.GONE);
}
@@ -285,24 +285,36 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
}
private boolean onLongClick() {
ArrayList<Child> filteredSongs = new ArrayList<>();
Bundle bundle = new Bundle();
switch (view) {
case Constants.DOWNLOAD_TYPE_TRACK:
bundle.putParcelable(Constants.TRACK_OBJECT, grouped.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
filteredSongs.add(grouped.get(getBindingAdapterPosition()));
break;
case Constants.DOWNLOAD_TYPE_ALBUM:
bundle.putString(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId());
click.onAlbumLongClick(bundle);
return true;
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ALBUM, grouped.get(getBindingAdapterPosition()).getAlbumId(), songs));
break;
case Constants.DOWNLOAD_TYPE_ARTIST:
bundle.putString(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId());
click.onArtistLongClick(bundle);
return true;
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_ARTIST, grouped.get(getBindingAdapterPosition()).getArtistId(), songs));
break;
case Constants.DOWNLOAD_TYPE_GENRE:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_GENRE, grouped.get(getBindingAdapterPosition()).getGenre(), songs));
break;
case Constants.DOWNLOAD_TYPE_YEAR:
filteredSongs.addAll(filterSong(Constants.DOWNLOAD_TYPE_YEAR, grouped.get(getBindingAdapterPosition()).getYear().toString(), songs));
break;
}
return false;
if (filteredSongs.isEmpty()) return false;
bundle.putParcelableArrayList(Constants.DOWNLOAD_GROUP, new ArrayList<>(filteredSongs));
bundle.putString(Constants.DOWNLOAD_GROUP_TITLE, item.downloadedItemTitleTextView.getText().toString());
bundle.putString(Constants.DOWNLOAD_GROUP_SUBTITLE, item.downloadedItemSubtitleTextView.getText().toString());
click.onDownloadGroupLongClick(bundle);
return true;
}
}
}

View File

@@ -39,7 +39,7 @@ public class GridTrackAdapter extends RecyclerView.Adapter<GridTrackAdapter.View
Chronology item = items.get(position);
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), item.getCoverArtId())
.from(holder.itemView.getContext(), item.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.trackCoverImageView);
}

View File

@@ -43,7 +43,7 @@ public class InternetRadioStationAdapter extends RecyclerView.Adapter<InternetRa
holder.item.internetRadioStationSubtitleTextView.setText(internetRadioStation.getStreamUrl());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl())
.from(holder.itemView.getContext(), internetRadioStation.getStreamUrl(), CustomGlideRequest.ResourceType.Radio)
.build()
.into(holder.item.internetRadioStationCoverImageView);
}

View File

@@ -43,13 +43,17 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
holder.item.musicDirectoryTitleTextView.setText(child.getTitle());
CustomGlideRequest.ResourceType type = child.isDir()
? CustomGlideRequest.ResourceType.Directory
: CustomGlideRequest.ResourceType.Song;
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), child.getCoverArtId())
.from(holder.itemView.getContext(), child.getCoverArtId(), type)
.build()
.into(holder.item.musicDirectoryCoverImageView);
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.GONE);
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.GONE : View.VISIBLE);
holder.item.musicDirectoryMoreButton.setVisibility(child.isDir() ? View.VISIBLE : View.INVISIBLE);
holder.item.musicDirectoryPlayButton.setVisibility(child.isDir() ? View.INVISIBLE : View.VISIBLE);
}
@Override
@@ -73,6 +77,8 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
item.musicDirectoryTitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.musicDirectoryMoreButton.setOnClickListener(v -> onClick());
}
@@ -88,5 +94,18 @@ public class MusicDirectoryAdapter extends RecyclerView.Adapter<MusicDirectoryAd
click.onMediaClick(bundle);
}
}
private boolean onLongClick() {
if (!children.get(getBindingAdapterPosition()).isDir()) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, children.get(getBindingAdapterPosition()));
click.onMediaLongClick(bundle);
return true;
} else {
return false;
}
}
}
}

View File

@@ -42,7 +42,7 @@ public class MusicFolderAdapter extends RecyclerView.Adapter<MusicFolderAdapter.
holder.item.musicFolderTitleTextView.setText(musicFolder.getName());
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), musicFolder.getName())
.from(holder.itemView.getContext(), musicFolder.getName(), CustomGlideRequest.ResourceType.Folder)
.build()
.into(holder.item.musicFolderCoverImageView);
}

View File

@@ -9,6 +9,7 @@ import androidx.media3.common.util.UnstableApi;
import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.databinding.ItemLibraryMusicIndexBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.FastScrollbar;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Artist;
@@ -42,10 +43,10 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
holder.item.musicIndexTitleTextView.setText(artist.getName());
/* CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getName())
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), artist.getName(), CustomGlideRequest.ResourceType.Directory)
.build()
.into(holder.item.musicIndexCoverImageView); */
.into(holder.item.musicIndexCoverImageView);
}
@Override
@@ -60,7 +61,7 @@ public class MusicIndexAdapter extends RecyclerView.Adapter<MusicIndexAdapter.Vi
@Override
public String getTextToShowInBubble(int pos) {
return Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0));
return artists != null && !artists.isEmpty() ? Character.toString(Objects.requireNonNull(artists.get(pos).getName().toUpperCase()).charAt(0)) : null;
}
public class ViewHolder extends RecyclerView.ViewHolder {

View File

@@ -49,7 +49,7 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
holder.item.queueSongSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.song_subtitle_formatter, MusicUtil.getReadableString(song.getArtist()), MusicUtil.getReadableDurationString(song.getDuration(), false)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.queueSongCoverImageView);

View File

@@ -37,7 +37,7 @@ public class PlaylistDialogSongHorizontalAdapter extends RecyclerView.Adapter<Pl
holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.playlistDialogSongCoverImageView);
}

View File

@@ -79,7 +79,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
holder.item.playlistSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), playlist.getCoverArtId())
.from(holder.itemView.getContext(), playlist.getCoverArtId(), CustomGlideRequest.ResourceType.Playlist)
.build()
.into(holder.item.playlistCoverImageView);
}

View File

@@ -75,7 +75,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId())
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(holder.item.podcastChannelCatalogueCoverImageView);
}

View File

@@ -42,7 +42,7 @@ public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<Podcas
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastChannel.getOriginalImageUrl())
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(holder.item.podcastChannelCoverImageView);
}

View File

@@ -51,7 +51,7 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId())
.from(holder.itemView.getContext(), podcastEpisode.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(holder.item.podcastCoverImageView);

View File

@@ -0,0 +1,99 @@
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.R;
import com.cappielloantonio.tempo.databinding.ItemHorizontalShareBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.UIUtil;
import java.util.Collections;
import java.util.List;
public class ShareHorizontalAdapter extends RecyclerView.Adapter<ShareHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private List<Share> shares;
public ShareHorizontalAdapter(ClickCallback click) {
this.click = click;
this.shares = Collections.emptyList();
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
ItemHorizontalShareBinding view = ItemHorizontalShareBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Share share = shares.get(position);
holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription()));
holder.item.shareSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
if (share.getEntries() != null && !share.getEntries().isEmpty()) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), share.getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(holder.item.shareCoverImageView);
}
@Override
public int getItemCount() {
return shares.size();
}
public void setItems(List<Share> shares) {
this.shares = shares;
notifyDataSetChanged();
}
public Share getItem(int id) {
return shares.get(id);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ItemHorizontalShareBinding item;
ViewHolder(ItemHorizontalShareBinding item) {
super(item.getRoot());
this.item = item;
item.shareTitleTextView.setSelected(true);
item.shareSubtitleTextView.setSelected(true);
itemView.setOnClickListener(v -> onClick());
itemView.setOnLongClickListener(v -> onLongClick());
item.shareButton.setOnClickListener(v -> onLongClick());
}
private void onClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
click.onShareClick(bundle);
}
private boolean onLongClick() {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.SHARE_OBJECT, shares.get(getBindingAdapterPosition()));
click.onShareLongClick(bundle);
return true;
}
}
}

View File

@@ -41,7 +41,7 @@ public class SimilarTrackAdapter extends RecyclerView.Adapter<SimilarTrackAdapte
holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle()));
CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.trackCoverImageView);
}

View File

@@ -25,13 +25,15 @@ import java.util.List;
@UnstableApi
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
private final ClickCallback click;
private final boolean isCoverVisible;
private final boolean showCoverArt;
private final boolean showAlbum;
private List<Child> songs;
public SongHorizontalAdapter(ClickCallback click, boolean isCoverVisible) {
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
this.click = click;
this.isCoverVisible = isCoverVisible;
this.showCoverArt = showCoverArt;
this.showAlbum = showAlbum;
this.songs = Collections.emptyList();
}
@@ -47,7 +49,7 @@ 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(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() != null ? song.getDuration() : 0, false)));
holder.item.trackNumberTextView.setText(MusicUtil.getReadableTrackNumber(holder.itemView.getContext(), song.getTrack()));
if (DownloadUtil.getDownloadTracker(holder.itemView.getContext()).isDownloaded(song.getId())) {
@@ -56,16 +58,15 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
holder.item.searchResultDowanloadIndicatorImageView.setVisibility(View.GONE);
}
if (isCoverVisible) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
if (showCoverArt) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.songCoverImageView);
if (isCoverVisible) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
holder.item.trackNumberTextView.setVisibility(showCoverArt ? View.INVISIBLE : View.VISIBLE);
holder.item.songCoverImageView.setVisibility(showCoverArt ? View.VISIBLE : View.INVISIBLE);
if (!isCoverVisible) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
if (!isCoverVisible && (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())) {
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);
}
}

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
@@ -14,38 +13,23 @@ import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class BatteryOptimizationDialog extends DialogFragment {
private static final String TAG = "BatteryOptimizationDialog";
private DialogBatteryOptimizationBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
DialogBatteryOptimizationBinding bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.activity_battery_optimizations_title)
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, listener) -> openPowerSettings())
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, listener) -> Preferences.dontAskForOptimization())
.setNegativeButton(R.string.battery_optimization_negative_button, null)
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
AlertDialog popup = builder.create();
popup.setCancelable(false);
popup.setCanceledOnTouchOutside(false);
return popup;
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
.create();
}
private void openPowerSettings() {

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
@@ -10,22 +9,18 @@ import androidx.fragment.app.DialogFragment;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogConnectionAlertBinding;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
public class ConnectionAlertDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogConnectionAlertBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
DialogConnectionAlertBinding bind = DialogConnectionAlertBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.connection_alert_dialog_title)
.setPositiveButton(R.string.connection_alert_dialog_positive_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.connection_alert_dialog_negative_button, (dialog, id) -> dialog.cancel());
@@ -45,16 +40,12 @@ public class ConnectionAlertDialog extends DialogFragment {
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Preferences.setDataSavingMode(true);
Objects.requireNonNull(getDialog()).dismiss();
});
}
}
}

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
@@ -12,26 +11,23 @@ import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDeleteDownloadStorageBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class DeleteDownloadStorageDialog extends DialogFragment {
private DialogDeleteDownloadStorageBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
DialogDeleteDownloadStorageBinding bind = DialogDeleteDownloadStorageBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.delete_download_storage_dialog_title)
.setPositiveButton(R.string.delete_download_storage_dialog_positive_button, null)
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null);
return builder.create();
.setNegativeButton(R.string.delete_download_storage_dialog_negative_button, null)
.create();
}
@Override
@@ -40,14 +36,8 @@ public class DeleteDownloadStorageDialog extends DialogFragment {
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = ((AlertDialog) getDialog());
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);

View File

@@ -0,0 +1,61 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogDownloadDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class DownloadDirectoryDialog extends DialogFragment {
private final DialogClickCallback dialogClickCallback;
public DownloadDirectoryDialog(DialogClickCallback dialogClickCallback) {
this.dialogClickCallback = dialogClickCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
DialogDownloadDirectoryBinding bind = DialogDownloadDirectoryBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.download_directory_dialog_title)
.setPositiveButton(R.string.download_directory_dialog_positive_button, null)
.setNegativeButton(R.string.download_directory_dialog_negative_button, null)
.create();
}
@Override
public void onResume() {
super.onResume();
setButtonAction();
}
private void setButtonAction() {
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
dialogClickCallback.onPositiveClick();
dialog.dismiss();
});
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
negativeButton.setOnClickListener(v -> {
dialogClickCallback.onNegativeClick();
dialog.dismiss();
});
}
}
}

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.widget.Button;
@@ -15,11 +14,10 @@ import com.cappielloantonio.tempo.databinding.DialogDownloadStorageBinding;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@OptIn(markerClass = UnstableApi.class)
public class DownloadStorageDialog extends DialogFragment {
private DialogDownloadStorageBinding bind;
private final DialogClickCallback dialogClickCallback;
public DownloadStorageDialog(DialogClickCallback dialogClickCallback) {
@@ -29,16 +27,14 @@ public class DownloadStorageDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
DialogDownloadStorageBinding bind = DialogDownloadStorageBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.download_storage_dialog_title)
.setPositiveButton(R.string.download_storage_external_dialog_positive_button, null)
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null);
return builder.create();
.setNegativeButton(R.string.download_storage_internal_dialog_negative_button, null)
.create();
}
@Override
@@ -47,14 +43,8 @@ public class DownloadStorageDialog extends DialogFragment {
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = ((AlertDialog) getDialog());
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.view.View;
@@ -17,6 +16,7 @@ import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlaylistChooserViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -30,17 +30,15 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistChooserBinding.inflate(getLayoutInflater());
playlistChooserViewModel = new ViewModelProvider(requireActivity()).get(PlaylistChooserViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.playlist_chooser_dialog_title)
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> {
})
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.playlist_chooser_dialog_neutral_button, (dialog, id) -> { })
.setNegativeButton(R.string.playlist_chooser_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -63,11 +61,12 @@ public class PlaylistChooserDialog extends DialogFragment implements ClickCallba
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.TRACK_OBJECT, playlistChooserViewModel.getSongToAdd());
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle);
dialog.show(requireActivity().getSupportFragmentManager(), null);

View File

@@ -1,9 +1,12 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
@@ -14,10 +17,13 @@ import androidx.recyclerview.widget.RecyclerView;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPlaylistEditorBinding;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.adapter.PlaylistDialogSongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlaylistEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Collections;
import java.util.Objects;
@@ -25,26 +31,29 @@ import java.util.Objects;
public class PlaylistEditorDialog extends DialogFragment {
private DialogPlaylistEditorBinding bind;
private PlaylistEditorViewModel playlistEditorViewModel;
private PlaylistCallback playlistCallback;
private String playlistName;
private PlaylistDialogSongHorizontalAdapter playlistDialogSongHorizontalAdapter;
public PlaylistEditorDialog(PlaylistCallback playlistCallback) {
this.playlistCallback = playlistCallback;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPlaylistEditorBinding.inflate(getLayoutInflater());
playlistEditorViewModel = new ViewModelProvider(requireActivity()).get(PlaylistEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.playlist_editor_dialog_title)
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> {
})
.setPositiveButton(R.string.playlist_editor_dialog_positive_button, (dialog, id) -> { })
.setNeutralButton(R.string.playlist_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNegativeButton(R.string.playlist_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -77,7 +86,9 @@ public class PlaylistEditorDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (playlistEditorViewModel.getSongToAdd() != null) {
playlistEditorViewModel.createPlaylist(playlistName);
@@ -85,14 +96,24 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistEditorViewModel.updatePlaylist(playlistName);
}
Objects.requireNonNull(getDialog()).dismiss();
dialogDismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
playlistEditorViewModel.deletePlaylist();
Objects.requireNonNull(getDialog()).dismiss();
dialogDismiss();
});
bind.playlistShareButton.setOnClickListener(view -> {
playlistEditorViewModel.sharePlaylist().observe(requireActivity(), sharedPlaylist -> {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedPlaylist.getUrl());
clipboardManager.setPrimaryClip(clipData);
});
});
bind.playlistShareButton.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
private void initSongsView() {
@@ -102,7 +123,9 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistDialogSongHorizontalAdapter = new PlaylistDialogSongHorizontalAdapter();
bind.playlistSongRecyclerView.setAdapter(playlistDialogSongHorizontalAdapter);
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> playlistDialogSongHorizontalAdapter.setItems(songs));
playlistEditorViewModel.getPlaylistSongLiveList().observe(requireActivity(), songs -> {
if (songs != null) playlistDialogSongHorizontalAdapter.setItems(songs);
});
new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) {
int originalPosition = -1;
@@ -157,4 +180,11 @@ public class PlaylistEditorDialog extends DialogFragment {
return true;
}
private void dialogDismiss() {
Objects.requireNonNull(getDialog()).dismiss();
if (playlistCallback != null) {
playlistCallback.onDismiss();
}
}
}

View File

@@ -1,9 +1,9 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
@@ -13,6 +13,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogPodcastChannelEditorBinding;
import com.cappielloantonio.tempo.interfaces.PodcastCallback;
import com.cappielloantonio.tempo.viewmodel.PodcastChannelEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -31,17 +32,15 @@ public class PodcastChannelEditorDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogPodcastChannelEditorBinding.inflate(getLayoutInflater());
podcastChannelEditorViewModel = new ViewModelProvider(requireActivity()).get(PodcastChannelEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.podcast_channel_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> { })
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -58,14 +57,19 @@ public class PodcastChannelEditorDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
podcastChannelEditorViewModel.createChannel(channelUrl);
dismissDialog();
}
});
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(v -> {
if (validateInput()) {
podcastChannelEditorViewModel.createChannel(channelUrl);
dismissDialog();
}
});
}
}
private boolean validateInput() {
channelUrl = Objects.requireNonNull(bind.podcastChannelRssUrlNameTextView.getText()).toString().trim();

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
@@ -15,6 +14,7 @@ import com.cappielloantonio.tempo.interfaces.RadioCallback;
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RadioEditorViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@@ -35,26 +35,36 @@ public class RadioEditorDialog extends DialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogRadioEditorBinding.inflate(getLayoutInflater());
radioEditorViewModel = new ViewModelProvider(requireActivity()).get(RadioEditorViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.radio_editor_dialog_title)
.setPositiveButton(R.string.radio_editor_dialog_positive_button, (dialog, id) -> {
if (validateInput()) {
if (radioEditorViewModel.getRadioToEdit() == null) {
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
} else {
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
}
dismissDialog();
}
})
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> dialog.cancel())
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.radio_editor_dialog_neutral_button, (dialog, id) -> {
radioEditorViewModel.deleteRadio();
dismissDialog();
})
.setNegativeButton(R.string.radio_editor_dialog_negative_button, (dialog, id) -> {
dialog.cancel();
})
.create();
}
@Override
public void onStart() {
super.onStart();
setParameterInfo();
setButtonAction();
}
@Override
@@ -75,25 +85,6 @@ public class RadioEditorDialog extends DialogFragment {
}
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
if (radioEditorViewModel.getRadioToEdit() == null) {
radioEditorViewModel.createRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
} else {
radioEditorViewModel.updateRadio(radioName, radioStreamURL, radioHomepageURL.isEmpty() ? null : radioHomepageURL);
}
dismissDialog();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
radioEditorViewModel.deleteRadio();
dismissDialog();
});
}
private boolean validateInput() {
radioName = Objects.requireNonNull(bind.internetRadioStationNameTextView.getText()).toString().trim();
radioStreamURL = Objects.requireNonNull(bind.internetRadioStationStreamUrlTextView.getText()).toString().trim();

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
@@ -12,6 +11,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogRatingBinding;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.RatingViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class RatingDialog extends DialogFragment {
private static final String TAG = "ServerSignupDialog";
@@ -25,14 +25,12 @@ public class RatingDialog extends DialogFragment {
bind = DialogRatingBinding.inflate(getLayoutInflater());
ratingViewModel = new ViewModelProvider(requireActivity()).get(RatingViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.rating_dialog_title)
.setNegativeButton(R.string.rating_dialog_negative_button, (dialog, id) -> dialog.cancel())
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()));
return builder.create();
.setPositiveButton(R.string.rating_dialog_positive_button, (dialog, id) -> ratingViewModel.rate((int) bind.ratingBar.getRating()))
.create();
}
@Override

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
@@ -14,6 +13,7 @@ import com.cappielloantonio.tempo.databinding.DialogServerSignupBinding;
import com.cappielloantonio.tempo.model.Server;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.viewmodel.LoginViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
import java.util.UUID;
@@ -33,21 +33,17 @@ public class ServerSignupDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
bind = DialogServerSignupBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class);
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.server_signup_dialog_title)
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> {
})
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel());
return builder.create();
.setNeutralButton(R.string.server_signup_dialog_neutral_button, (dialog, id) -> { })
.setPositiveButton(R.string.server_signup_dialog_positive_button, (dialog, id) -> { })
.setNegativeButton(R.string.server_signup_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
@@ -81,14 +77,16 @@ public class ServerSignupDialog extends DialogFragment {
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
saveServerPreference();
Objects.requireNonNull(getDialog()).dismiss();
}
});
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
loginViewModel.deleteServer(null);
Objects.requireNonNull(getDialog()).dismiss();
});

View File

@@ -1,11 +1,11 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.media3.common.util.UnstableApi;
@@ -13,34 +13,32 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogServerUnreachableBinding;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Objects;
@OptIn(markerClass = UnstableApi.class)
public class ServerUnreachableDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogServerUnreachableBinding bind;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogServerUnreachableBinding.inflate(getLayoutInflater());
DialogServerUnreachableBinding bind = DialogServerUnreachableBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
AlertDialog popup = new MaterialAlertDialogBuilder(getActivity()).setView(bind.getRoot())
.setTitle(R.string.server_unreachable_dialog_title)
.setPositiveButton(R.string.server_unreachable_dialog_positive_button, null)
.setNeutralButton(R.string.server_unreachable_dialog_neutral_button, null)
.setNegativeButton(R.string.server_unreachable_dialog_negative_button, (dialog, id) -> dialog.cancel());
.setNegativeButton(R.string.server_unreachable_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
AlertDialog popup = builder.create();
popup.setCancelable(false);
popup.setCanceledOnTouchOutside(false);
popup.setCancelable(false);
return popup;
}
@Override
public void onStart() {
super.onStart();
@@ -48,26 +46,18 @@ public class ServerUnreachableDialog extends DialogFragment {
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction() {
AlertDialog dialog = (AlertDialog) getDialog();
androidx.appcompat.app.AlertDialog alertDialog = (androidx.appcompat.app.AlertDialog) Objects.requireNonNull(getDialog());
if(dialog != null) {
(dialog).getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
MainActivity activity = (MainActivity) getActivity();
if (activity != null) activity.quit();
dialog.dismiss();
});
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
MainActivity activity = (MainActivity) getActivity();
if (activity != null) activity.quit();
alertDialog.dismiss();
});
(dialog).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
dialog.dismiss();
});
}
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
alertDialog.dismiss();
});
}
}

View File

@@ -0,0 +1,133 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogShareUpdateBinding;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
import com.google.android.material.datepicker.CalendarConstraints;
import com.google.android.material.datepicker.DateValidatorPointForward;
import com.google.android.material.datepicker.MaterialDatePicker;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.Date;
import java.util.Objects;
public class ShareUpdateDialog extends DialogFragment {
private DialogShareUpdateBinding bind;
private HomeViewModel homeViewModel;
private ShareBottomSheetViewModel shareBottomSheetViewModel;
private MaterialDatePicker<Long> datePicker;
private String descriptionTextView;
private String expirationTextView;
private long expiration;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
shareBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ShareBottomSheetViewModel.class);
bind = DialogShareUpdateBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(requireContext())
.setView(bind.getRoot())
.setTitle(R.string.share_update_dialog_title)
.setPositiveButton(R.string.share_update_dialog_positive_button, (dialog, id) -> {
})
.setNegativeButton(R.string.share_update_dialog_negative_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
public void onStart() {
super.onStart();
setShareInfo();
setShareCalendar();
setButtonAction();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setShareInfo() {
if (shareBottomSheetViewModel.getShare() != null) {
bind.shareDescriptionTextView.setText(shareBottomSheetViewModel.getShare().getDescription());
// bind.shareExpirationTextView.setText(shareBottomSheetViewModel.getShare().getExpires());
}
}
private void setShareCalendar() {
expiration = shareBottomSheetViewModel.getShare().getExpires().getTime();
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(expiration)));
bind.shareExpirationTextView.setFocusable(false);
bind.shareExpirationTextView.setOnLongClickListener(null);
bind.shareExpirationTextView.setOnClickListener(view -> {
CalendarConstraints constraints = new CalendarConstraints.Builder()
.setValidator(DateValidatorPointForward.now())
.build();
datePicker = MaterialDatePicker.Builder.datePicker()
.setCalendarConstraints(constraints)
.setSelection(expiration)
.build();
datePicker.addOnPositiveButtonClickListener(selection -> {
expiration = selection;
bind.shareExpirationTextView.setText(UIUtil.getReadableDate(new Date(selection)));
});
datePicker.show(requireActivity().getSupportFragmentManager(), null);
});
}
private void setButtonAction() {
((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
if (validateInput()) {
updateShare();
Objects.requireNonNull(getDialog()).dismiss();
}
});
}
private boolean validateInput() {
descriptionTextView = Objects.requireNonNull(bind.shareDescriptionTextView.getText()).toString().trim();
expirationTextView = Objects.requireNonNull(bind.shareExpirationTextView.getText()).toString().trim();
if (TextUtils.isEmpty(descriptionTextView)) {
bind.shareDescriptionTextView.setError(getString(R.string.error_required));
return false;
}
if (TextUtils.isEmpty(expirationTextView)) {
bind.shareExpirationTextView.setError(getString(R.string.error_required));
return false;
}
return true;
}
private void updateShare() {
shareBottomSheetViewModel.updateShare(descriptionTextView, expiration);
homeViewModel.refreshShares(requireActivity());
}
}

View File

@@ -1,6 +1,5 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
@@ -19,31 +18,28 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.StarredSyncViewModel;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.stream.Collectors;
@OptIn(markerClass = UnstableApi.class)
public class StarredSyncDialog extends DialogFragment {
private static final String TAG = "ServerUnreachableDialog";
private DialogStarredSyncBinding bind;
private StarredSyncViewModel starredSyncViewModel;
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
DialogStarredSyncBinding bind = DialogStarredSyncBinding.inflate(getLayoutInflater());
starredSyncViewModel = new ViewModelProvider(requireActivity()).get(StarredSyncViewModel.class);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setTitle(R.string.starred_sync_dialog_title)
.setPositiveButton(R.string.starred_sync_dialog_positive_button, null)
.setNeutralButton(R.string.starred_sync_dialog_neutral_button, null)
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null);
return builder.create();
.setNegativeButton(R.string.starred_sync_dialog_negative_button, null)
.create();
}
@Override
@@ -52,14 +48,8 @@ public class StarredSyncDialog extends DialogFragment {
setButtonAction(requireContext());
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setButtonAction(Context context) {
AlertDialog dialog = ((AlertDialog) getDialog());
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
if (dialog != null) {
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);

View File

@@ -0,0 +1,128 @@
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.media3.common.MediaMetadata;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.DialogTrackInfoBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
public class TrackInfoDialog extends DialogFragment {
private DialogTrackInfoBinding bind;
private MediaMetadata mediaMetadata;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
return new MaterialAlertDialogBuilder(getActivity())
.setView(bind.getRoot())
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel())
.create();
}
@Override
public void onStart() {
super.onStart();
setTrackInfo();
setTrackTranscodingInfo();
}
@Override
public void onDestroyView() {
super.onDestroyView();
bind = null;
}
private void setTrackInfo() {
bind.trakTitleInfoTextView.setText(mediaMetadata.title);
bind.trakArtistInfoTextView.setText(mediaMetadata.artist);
if (mediaMetadata.extras != null) {
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId", ""), CustomGlideRequest.ResourceType.Song)
.build()
.into(bind.trackCoverInfoImageView);
bind.titleValueSector.setText(mediaMetadata.extras.getString("title", getString(R.string.label_placeholder)));
bind.albumValueSector.setText(mediaMetadata.extras.getString("album", getString(R.string.label_placeholder)));
bind.artistValueSector.setText(mediaMetadata.extras.getString("artist", getString(R.string.label_placeholder)));
bind.trackNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("track", 0)));
bind.yearValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("year", 0)));
bind.genreValueSector.setText(mediaMetadata.extras.getString("genre", getString(R.string.label_placeholder)));
bind.sizeValueSector.setText(MusicUtil.getReadableByteCount(mediaMetadata.extras.getLong("size", 0)));
bind.contentTypeValueSector.setText(mediaMetadata.extras.getString("contentType", getString(R.string.label_placeholder)));
bind.suffixValueSector.setText(mediaMetadata.extras.getString("suffix", getString(R.string.label_placeholder)));
bind.transcodedContentTypeValueSector.setText(mediaMetadata.extras.getString("transcodedContentType", getString(R.string.label_placeholder)));
bind.transcodedSuffixValueSector.setText(mediaMetadata.extras.getString("transcodedSuffix", getString(R.string.label_placeholder)));
bind.durationValueSector.setText(MusicUtil.getReadableDurationString(mediaMetadata.extras.getInt("duration", 0), false));
bind.bitrateValueSector.setText(mediaMetadata.extras.getInt("bitrate", 0) + " kbps");
bind.pathValueSector.setText(mediaMetadata.extras.getString("path", getString(R.string.label_placeholder)));
bind.discNumberValueSector.setText(String.valueOf(mediaMetadata.extras.getInt("discNumber", 0)));
}
}
private void setTrackTranscodingInfo() {
StringBuilder info = new StringBuilder();
boolean prioritizeServerTranscoding = Preferences.isServerPrioritized();
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0 ? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps" : "Original";
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
info.append(getString(R.string.track_info_summary_downloaded_file));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (prioritizeServerTranscoding) {
info.append(getString(R.string.track_info_summary_server_prioritized));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_original_file));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_transcoding_codec, transcodingExtension));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_transcoding_bitrate, transcodingBitrate));
bind.trakTranscodingInfoTextView.setText(info);
return;
}
if (!prioritizeServerTranscoding && !transcodingExtension.equals("raw") && !transcodingBitrate.equals("Original")) {
info.append(getString(R.string.track_info_summary_full_transcode, transcodingExtension, transcodingBitrate));
bind.trakTranscodingInfoTextView.setText(info);
}
}
}

View File

@@ -172,7 +172,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
private void initBackCover() {
CustomGlideRequest.Builder
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId())
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(bind.albumCoverImageView);
}
@@ -181,7 +181,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, false);
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));

View File

@@ -16,17 +16,20 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.session.MediaBrowser;
import androidx.media3.session.SessionToken;
import androidx.navigation.Navigation;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentArtistPageBinding;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
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.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumArtistPageOrSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumCatalogueAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistSimilarAdapter;
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
import com.cappielloantonio.tempo.util.Constants;
@@ -42,6 +45,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private SongHorizontalAdapter songHorizontalAdapter;
private AlbumArtistPageOrSimilarAdapter albumArtistPageOrSimilarAdapter;
private AlbumCatalogueAdapter albumCatalogueAdapter;
private ArtistSimilarAdapter artistSimilarAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@@ -59,7 +63,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
initArtistInfo();
initPlayButtons();
initTopSongsView();
initAlbumsView();
initHorizontalAlbumsView();
initVerticalAlbumsView();
initSimilarArtistsView();
return view;
@@ -93,6 +98,13 @@ 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() {
@@ -107,8 +119,9 @@ 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 (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());
@@ -119,7 +132,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.bioMoreTextViewClickable.setVisibility(artistInfo.getLastFmUrl() != null ? View.VISIBLE : View.GONE);
if (getContext() != null && bind != null) CustomGlideRequest.Builder
.from(requireContext(), artistPageViewModel.getArtist().getId())
.from(requireContext(), artistPageViewModel.getArtist().getId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(bind.artistBackdropImageView);
@@ -131,7 +144,8 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
startActivity(intent);
});
if (bind != null) bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageBioPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null) bind.artistPageBioSector.setVisibility(View.VISIBLE);
}
});
@@ -164,38 +178,67 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
private void initTopSongsView() {
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
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.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.artistPageTopTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.artistPageTopSongsSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
songHorizontalAdapter.setItems(songs);
}
});
}
private void initAlbumsView() {
bind.albumsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
private void initHorizontalAlbumsView() {
bind.albumsHorizontalRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false));
albumArtistPageOrSimilarAdapter = new AlbumArtistPageOrSimilarAdapter(this);
bind.albumsRecyclerView.setAdapter(albumArtistPageOrSimilarAdapter);
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.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);
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.albumsRecyclerView);
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);
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);
}
});
}
private void initSimilarArtistsView() {
@@ -206,11 +249,14 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
bind.similarArtistsRecyclerView.setAdapter(artistSimilarAdapter);
artistPageViewModel.getArtistInfo(artistPageViewModel.getArtist().getId()).observe(getViewLifecycleOwner(), artist -> {
if (artist == null) {
if (bind != null) bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.VISIBLE);
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) bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.artistPageSimilarArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.similarArtistSector.setVisibility(!artist.getSimilarArtists().isEmpty() ? View.VISIBLE : View.GONE);
artistSimilarAdapter.setItems(artist.getSimilarArtists());
}
});

View File

@@ -23,12 +23,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentDirectoryBinding;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.MusicDirectoryAdapter;
import com.cappielloantonio.tempo.ui.dialog.DownloadDirectoryDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
@@ -50,6 +52,8 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private MenuItem menuItem;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -60,6 +64,8 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
inflater.inflate(R.menu.directory_page_menu, menu);
menuItem = menu.getItem(0);
}
@Override
@@ -97,15 +103,23 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.action_download_directory) {
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
if (isVisible() && getActivity() != null) {
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
DownloadDirectoryDialog dialog = new DownloadDirectoryDialog(new DialogClickCallback() {
@Override
public void onPositiveClick() {
directoryViewModel.loadMusicDirectory(getArguments().getString(Constants.MUSIC_DIRECTORY_ID)).observe(getViewLifecycleOwner(), directory -> {
if (isVisible() && getActivity() != null) {
List<Child> songs = directory.getChildren().stream().filter(child -> !child.isDir()).collect(Collectors.toList());
DownloadUtil.getDownloadTracker(requireContext()).download(
MappingUtil.mapDownloads(songs),
songs.stream().map(Download::new).collect(Collectors.toList())
);
}
});
}
});
dialog.show(activity.getSupportFragmentManager(), null);
return true;
}
@@ -144,6 +158,14 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
bind.directoryTitleLabel.setText(directory.getName());
musicDirectoryAdapter.setItems(directory.getChildren());
menuItem.setVisible(
directory.getChildren() != null && directory.getChildren()
.stream()
.filter(child -> !child.isDir())
.findFirst()
.orElse(null) != null
);
});
}
@@ -160,6 +182,11 @@ public class DirectoryFragment extends Fragment implements ClickCallback {
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
}
@Override
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onMusicDirectoryClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.directoryFragment, bundle);

View File

@@ -7,6 +7,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.PopupMenu;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
@@ -37,6 +38,8 @@ import java.util.Objects;
@UnstableApi
public class DownloadFragment extends Fragment implements ClickCallback {
private static final String TAG = "DownloadFragment";
private FragmentDownloadBinding bind;
private MainActivity activity;
private DownloadViewModel downloadViewModel;
@@ -160,6 +163,23 @@ public class DownloadFragment extends Fragment implements ClickCallback {
}
bind.downloadedGoBackImageView.setVisibility(stack.size() > 1 ? View.VISIBLE : View.GONE);
setupBackPressing(stack.size());
});
}
private void setupBackPressing(int stackSize) {
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() {
if (stackSize > 1) {
downloadViewModel.popViewStack();
} else {
activity.navController.navigateUp();
}
remove();
}
});
}
@@ -234,4 +254,9 @@ public class DownloadFragment extends Fragment implements ClickCallback {
public void onMediaLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songBottomSheetDialog, bundle);
}
@Override
public void onDownloadGroupLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.downloadBottomSheetDialog, bundle);
}
}

View File

@@ -1,11 +1,13 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -34,6 +36,7 @@ import com.cappielloantonio.tempo.service.DownloaderManager;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
@@ -41,6 +44,7 @@ import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
import com.cappielloantonio.tempo.ui.adapter.DiscoverSongAdapter;
import com.cappielloantonio.tempo.ui.adapter.GridTrackAdapter;
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;
@@ -77,6 +81,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private AlbumHorizontalAdapter newReleasesAlbumAdapter;
private YearAdapter yearAdapter;
private GridTrackAdapter gridTrackAdapter;
private ShareHorizontalAdapter shareHorizontalAdapter;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@@ -112,6 +117,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initYearSongView();
initRecentAddedAlbumView();
initGridView();
initSharesView();
}
@Override
@@ -121,6 +127,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initializeMediaBrowser();
}
@Override
public void onResume() {
super.onResume();
refreshSharesView();
}
@Override
public void onStop() {
releaseMediaBrowser();
@@ -228,6 +240,11 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
homeViewModel.refreshMostRecentlyAddedAlbums(getViewLifecycleOwner());
return true;
});
bind.sharesTextViewRefreshable.setOnLongClickListener(v -> {
homeViewModel.refreshShares(getViewLifecycleOwner());
return true;
});
}
private void initSyncStarredView() {
@@ -290,11 +307,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.discoverSongViewPager.setOffscreenPageLimit(1);
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.VISIBLE);
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);
if (bind != null)
bind.homeDiscoveryPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeDiscoverSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
discoverSongAdapter.setItems(songs);
}
@@ -311,11 +331,14 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
bind.similarTracksRecyclerView.setAdapter(similarMusicAdapter);
homeViewModel.getStarredTracksSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
if (songs == null) {
if (bind != null) bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.VISIBLE);
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);
if (bind != null)
bind.homeSimilarTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeSimilarTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
similarMusicAdapter.setItems(songs);
}
@@ -333,11 +356,14 @@ 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.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);
if (bind != null)
bind.homeBestOfArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeBestOfArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
bestOfArtistAdapter.setItems(artists);
}
@@ -355,12 +381,16 @@ 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.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) bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.homeRadioArtistPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRadioArtistSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.afterRadioArtistDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
radioArtistAdapter.setItems(artists);
}
@@ -378,14 +408,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
gridTrackAdapter = new GridTrackAdapter(this);
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.size() == 0) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
gridTrackAdapter.setItems(chronologies);
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
if (music != null) {
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
if (chronologies == null || chronologies.size() == 0) {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.GONE);
if (bind != null) bind.afterGridDivider.setVisibility(View.GONE);
} else {
if (bind != null) bind.homeGridTracksSector.setVisibility(View.VISIBLE);
if (bind != null) bind.afterGridDivider.setVisibility(View.VISIBLE);
gridTrackAdapter.setItems(chronologies);
}
});
}
});
}
@@ -393,16 +427,20 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
private void initStarredTracksView() {
bind.starredTracksRecyclerView.setHasFixedSize(true);
starredSongAdapter = new SongHorizontalAdapter(this, 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.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) bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
if (bind != null)
bind.starredTracksPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredTracksSector.setVisibility(!songs.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.starredTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(songs.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredSongAdapter.setItems(songs);
}
@@ -428,12 +466,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
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.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) bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
if (bind != null)
bind.starredAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.starredAlbumsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredAlbumAdapter.setItems(albums);
}
@@ -459,13 +501,18 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
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.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) bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null) bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
if (bind != null)
bind.starredArtistsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.starredArtistsSector.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.afterFavoritesDivider.setVisibility(!artists.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.starredArtistsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(artists.size(), 5), GridLayoutManager.HORIZONTAL, false));
starredArtistAdapter.setItems(artists);
}
@@ -491,12 +538,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
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.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) bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
if (bind != null)
bind.homeNewReleasesPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeNewReleasesSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
if (bind != null)
bind.newReleasesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(albums.size(), 5), GridLayoutManager.HORIZONTAL, false));
newReleasesAlbumAdapter.setItems(albums);
}
@@ -523,11 +574,14 @@ 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.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);
if (bind != null)
bind.homeFlashbackPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeFlashbackSector.setVisibility(!years.isEmpty() ? View.VISIBLE : View.GONE);
yearAdapter.setItems(years);
}
@@ -545,11 +599,14 @@ 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.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 (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);
@@ -568,11 +625,14 @@ 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.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);
if (bind != null)
bind.homeRecentlyPlayedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyPlayedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyPlayedAlbumAdapter.setItems(albums);
}
@@ -590,11 +650,14 @@ 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.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);
if (bind != null)
bind.homeRecentlyAddedAlbumsPlaceholder.placeholder.setVisibility(View.GONE);
if (bind != null)
bind.homeRecentlyAddedAlbumsSector.setVisibility(!albums.isEmpty() ? View.VISIBLE : View.GONE);
recentlyAddedAlbumAdapter.setItems(albums);
}
@@ -604,6 +667,52 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
}
private void initSharesView() {
bind.sharesRecyclerView.setHasFixedSize(true);
shareHorizontalAdapter = new ShareHorizontalAdapter(this);
bind.sharesRecyclerView.setAdapter(shareHorizontalAdapter);
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)
bind.sharesRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(shares.size(), 10), GridLayoutManager.HORIZONTAL, false));
shareHorizontalAdapter.setItems(shares);
}
});
}
SnapHelper starredTrackSnapHelper = new PagerSnapHelper();
starredTrackSnapHelper.attachToRecyclerView(bind.sharesRecyclerView);
bind.sharesRecyclerView.addItemDecoration(
new DotsIndicatorDecoration(
getResources().getDimensionPixelSize(R.dimen.radius),
getResources().getDimensionPixelSize(R.dimen.radius) * 4,
getResources().getDimensionPixelSize(R.dimen.dots_height),
requireContext().getResources().getColor(R.color.titleTextColor, null),
requireContext().getResources().getColor(R.color.titleTextColor, null))
);
}
private void refreshSharesView() {
final Handler handler = new Handler();
final Runnable runnable = () -> {
if (getView() != null && bind != null && Preferences.isSharingEnabled()) {
homeViewModel.refreshShares(getViewLifecycleOwner());
}
};
handler.postDelayed(runnable, 100);
}
private void setSlideViewOffset(ViewPager2 viewPager, float pageOffset, float pageMargin) {
viewPager.setPageTransformer((page, position) -> {
float myOffset = position * -(2 * pageOffset + pageMargin);
@@ -619,25 +728,6 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
});
}
public void reorder() {
if (bind != null) {
// bind.homeLinearLayoutContainer.removeAllViews();
// bind.homeLinearLayoutContainer.addView(bind.homeDiscoverSector);
// bind.homeLinearLayoutContainer.addView(bind.homeSimilarTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRadioArtistSector);
// bind.homeLinearLayoutContainer.addView(bind.homeGridTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredTracksSector);
// bind.homeLinearLayoutContainer.addView(bind.starredAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.starredArtistsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeFlashbackSector);
// bind.homeLinearLayoutContainer.addView(bind.homeMostPlayedAlbumsSector);
// bind.homeLinearLayoutContainer.addView(bind.homeRecentlyPlayedAlbumsSector);
}
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
@@ -722,4 +812,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
public void onYearClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.songListPageFragment, bundle);
}
@Override
public void onShareClick(Bundle bundle) {
Share share = bundle.getParcelable(Constants.SHARE_OBJECT);
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(share.getUrl())).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
@Override
public void onShareLongClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
}
}

View File

@@ -85,13 +85,15 @@ public class IndexFragment extends Fragment implements ClickCallback {
}
private void initDirectoryListView() {
MusicFolder musicFolder = getArguments().getParcelable(Constants.MUSIC_FOLDER_OBJECT);
bind.indexRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.indexRecyclerView.setHasFixedSize(true);
musicIndexAdapter = new MusicIndexAdapter(this);
bind.indexRecyclerView.setAdapter(musicIndexAdapter);
indexViewModel.getIndexes().observe(getViewLifecycleOwner(), indexes -> {
indexViewModel.getIndexes(musicFolder != null ? musicFolder.getId() : null).observe(getViewLifecycleOwner(), indexes -> {
if (indexes != null) {
musicIndexAdapter.setItems(IndexUtil.getArtist(indexes));
}

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.ui.fragment;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -18,6 +19,7 @@ import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.databinding.FragmentLibraryBinding;
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
import com.cappielloantonio.tempo.interfaces.ClickCallback;
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
@@ -71,7 +73,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
initAlbumView();
initArtistView();
initGenreView();
initPlaylistSlideView();
initPlaylistView();
}
@Override
@@ -80,6 +82,12 @@ public class LibraryFragment extends Fragment implements ClickCallback {
activity.setBottomNavigationBarVisibility(true);
}
@Override
public void onResume() {
super.onResume();
refreshPlaylistView();
}
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -222,7 +230,7 @@ public class LibraryFragment extends Fragment implements ClickCallback {
genreSnapHelper.attachToRecyclerView(bind.genreRecyclerView);
}
private void initPlaylistSlideView() {
private void initPlaylistView() {
bind.playlistRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.playlistRecyclerView.setHasFixedSize(true);
@@ -244,6 +252,17 @@ public class LibraryFragment extends Fragment implements ClickCallback {
});
}
private void refreshPlaylistView() {
final Handler handler = new Handler();
final Runnable runnable = () -> {
if (getView() != null && bind != null && libraryViewModel != null)
libraryViewModel.refreshPlaylistSample(getViewLifecycleOwner());
};
handler.postDelayed(runnable, 100);
}
@Override
public void onAlbumClick(Bundle bundle) {
Navigation.findNavController(requireView()).navigate(R.id.albumPageFragment, bundle);
@@ -276,7 +295,13 @@ public class LibraryFragment extends Fragment implements ClickCallback {
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
@Override
public void onDismiss() {
refreshPlaylistView();
}
});
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
}

View File

@@ -37,6 +37,7 @@ import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects;
import java.util.stream.IntStream;
@OptIn(markerClass = UnstableApi.class)
@@ -156,14 +157,18 @@ public class PlayerBottomSheetFragment extends Fragment {
if (mediaMetadata.extras != null) {
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
playerBottomSheetViewModel.setLiveDescription(mediaMetadata.extras.getString("description", null));
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("title")));
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"))
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"), CustomGlideRequest.ResourceType.Song)
.build()
.into(bind.playerHeaderLayout.playerHeaderMediaCoverImage);
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setVisibility(mediaMetadata.extras.getString("title") != null && !Objects.equals(mediaMetadata.extras.getString("title"), "") ? View.VISIBLE : View.GONE);
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setVisibility(mediaMetadata.extras.getString("artist") != null && !Objects.equals(mediaMetadata.extras.getString("artist"), "") ? View.VISIBLE : View.GONE);
}
}
@@ -240,6 +245,10 @@ public class PlayerBottomSheetFragment extends Fragment {
}
}
public void goToQueuePage() {
bind.playerBodyLayout.playerBodyBottomSheetViewPager.setCurrentItem(1, true);
}
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
progressBarHandler = new Handler();
progressBarRunnable = () -> {

View File

@@ -6,12 +6,12 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.ToggleButton;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.MediaMetadata;
@@ -29,15 +29,19 @@ import com.cappielloantonio.tempo.databinding.InnerFragmentPlayerControllerBindi
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.ui.dialog.RatingDialog;
import com.cappielloantonio.tempo.ui.dialog.TrackInfoDialog;
import com.cappielloantonio.tempo.ui.fragment.pager.PlayerControllerHorizontalPager;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.android.material.chip.Chip;
import com.google.android.material.elevation.SurfaceColors;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.Objects;
@UnstableApi
public class PlayerControllerFragment extends Fragment {
private static final String TAG = "PlayerCoverFragment";
@@ -51,10 +55,8 @@ public class PlayerControllerFragment extends Fragment {
private ToggleButton skipSilenceToggleButton;
private Chip playerMediaExtension;
private TextView playerMediaBitrate;
private ImageView playerMediaTranscodingIcon;
private ImageView playerMediaTranscodingPriorityIcon;
private Chip playerMediaTranscodedExtension;
private TextView playerMediaTranscodedBitrate;
private ConstraintLayout playerQuickActionView;
private ImageButton playerTrackInfo;
private MainActivity activity;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@@ -70,10 +72,10 @@ public class PlayerControllerFragment extends Fragment {
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
initQuickActionView();
initCoverLyricsSlideView();
initMediaListenable();
initArtistLabelButton();
initTranscodingInfo();
return view;
}
@@ -106,10 +108,19 @@ public class PlayerControllerFragment extends Fragment {
skipSilenceToggleButton = bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button);
playerMediaExtension = bind.getRoot().findViewById(R.id.player_media_extension);
playerMediaBitrate = bind.getRoot().findViewById(R.id.player_media_bitrate);
playerMediaTranscodingIcon = bind.getRoot().findViewById(R.id.player_media_transcoding_audio);
playerMediaTranscodingPriorityIcon = bind.getRoot().findViewById(R.id.player_media_server_transcode_priority);
playerMediaTranscodedExtension = bind.getRoot().findViewById(R.id.player_media_transcoded_extension);
playerMediaTranscodedBitrate = bind.getRoot().findViewById(R.id.player_media_transcoded_bitrate);
playerQuickActionView = bind.getRoot().findViewById(R.id.player_quick_action_view);
playerTrackInfo = bind.getRoot().findViewById(R.id.player_info_track);
}
private void initQuickActionView() {
playerQuickActionView.setBackgroundColor(SurfaceColors.getColorForElevation(requireContext(), 8));
playerQuickActionView.setOnClickListener(view -> {
PlayerBottomSheetFragment playerBottomSheetFragment = (PlayerBottomSheetFragment) requireActivity().getSupportFragmentManager().findFragmentByTag("PlayerBottomSheet");
if (playerBottomSheetFragment != null) {
playerBottomSheetFragment.goToQueuePage();
}
});
}
private void initializeBrowser() {
@@ -155,14 +166,15 @@ public class PlayerControllerFragment extends Fragment {
playerMediaTitleLabel.setSelected(true);
playerArtistNameLabel.setSelected(true);
playerMediaTitleLabel.setVisibility(mediaMetadata.title != null && !Objects.equals(mediaMetadata.title, "") ? View.VISIBLE : View.GONE);
playerArtistNameLabel.setVisibility(mediaMetadata.artist != null && !Objects.equals(mediaMetadata.artist, "") ? View.VISIBLE : View.GONE);
}
private void setMediaInfo(MediaMetadata mediaMetadata) {
if (mediaMetadata.extras != null) {
String extension = mediaMetadata.extras.getString("suffix", "Unknown format");
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0
? mediaMetadata.extras.getInt("bitrate", 0) + "kbps"
: "Original";
String bitrate = mediaMetadata.extras.getInt("bitrate", 0) != 0 ? mediaMetadata.extras.getInt("bitrate", 0) + "kbps" : "Original";
playerMediaExtension.setText(extension);
@@ -174,38 +186,18 @@ public class PlayerControllerFragment extends Fragment {
}
}
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
: "Original";
boolean isTranscodingExtension = !MusicUtil.getTranscodingFormatPreference().equals("raw");
boolean isTranscodingBitrate = !MusicUtil.getBitratePreference().equals("0");
if (transcodingExtension.equals("raw") && transcodingBitrate.equals("Original")) {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
} else {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.VISIBLE);
playerMediaTranscodedBitrate.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setVisibility(View.VISIBLE);
playerMediaTranscodedExtension.setText(transcodingExtension);
playerMediaTranscodedBitrate.setText(transcodingBitrate);
if (isTranscodingExtension || isTranscodingBitrate) {
playerMediaExtension.setText("Transcoding");
playerMediaBitrate.setText("requested");
}
if (mediaMetadata.extras != null && mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingPriorityIcon.setVisibility(View.GONE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
if (Preferences.isServerPrioritized() && mediaMetadata.extras != null && !mediaMetadata.extras.getString("uri", "").contains(Constants.DOWNLOAD_URI)) {
playerMediaTranscodingPriorityIcon.setVisibility(View.VISIBLE);
playerMediaTranscodingIcon.setVisibility(View.GONE);
playerMediaTranscodedBitrate.setVisibility(View.GONE);
playerMediaTranscodedExtension.setVisibility(View.GONE);
}
playerTrackInfo.setOnClickListener(view -> {
TrackInfoDialog dialog = new TrackInfoDialog(mediaMetadata);
dialog.show(activity.getSupportFragmentManager(), null);
});
}
private void setMediaControllerUI(MediaBrowser mediaBrowser) {
@@ -222,6 +214,7 @@ public class PlayerControllerFragment extends Fragment {
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.VISIBLE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.VISIBLE);
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.GONE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_RADIO:
@@ -233,6 +226,7 @@ public class PlayerControllerFragment extends Fragment {
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.GONE);
setPlaybackParameters(mediaBrowser);
break;
case Constants.MEDIA_TYPE_MUSIC:
@@ -245,6 +239,7 @@ public class PlayerControllerFragment extends Fragment {
bind.getRoot().setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE);
bind.getRoot().findViewById(R.id.player_playback_speed_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.player_skip_silence_toggle_button).setVisibility(View.GONE);
bind.getRoot().findViewById(R.id.button_favorite).setVisibility(View.VISIBLE);
resetPlaybackParameters(mediaBrowser);
break;
}
@@ -305,13 +300,6 @@ public class PlayerControllerFragment extends Fragment {
});
}
private void initTranscodingInfo() {
playerMediaTranscodingPriorityIcon.setOnLongClickListener(view -> {
Toast.makeText(requireActivity(), R.string.settings_audio_transcode_priority_toast, Toast.LENGTH_SHORT).show();
return true;
});
}
private void initPlaybackSpeedButton(MediaBrowser mediaBrowser) {
playbackSpeedButton.setOnClickListener(view -> {
float currentSpeed = Preferences.getPlaybackSpeed();

View File

@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.ui.fragment;
import android.content.ComponentName;
import android.os.Bundle;
import android.os.Handler;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionManager;
@@ -39,6 +40,8 @@ public class PlayerCoverFragment extends Fragment {
private InnerFragmentPlayerCoverBinding bind;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
private final Handler handler = new Handler();
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
bind = InnerFragmentPlayerCoverBinding.inflate(inflater, container, false);
@@ -72,9 +75,19 @@ public class PlayerCoverFragment extends Fragment {
bind = null;
}
private void initTapButtonHideTransition() {
bind.nowPlayingTapButton.setVisibility(View.VISIBLE);
handler.removeCallbacksAndMessages(null);
final Runnable runnable = () -> bind.nowPlayingTapButton.setVisibility(View.GONE);
handler.postDelayed(runnable, 10000);
}
private void initOverlay() {
bind.nowPlayingSongCoverImageView.setOnClickListener(view -> toggleOverlayVisibility(true));
bind.nowPlayingSongCoverButtonGroup.setOnClickListener(view -> toggleOverlayVisibility(false));
bind.nowPlayingTapButton.setOnClickListener(view -> toggleOverlayVisibility(true));
}
private void toggleOverlayVisibility(boolean isVisible) {
@@ -84,9 +97,12 @@ public class PlayerCoverFragment extends Fragment {
TransitionManager.beginDelayedTransition(bind.getRoot(), transition);
bind.nowPlayingSongCoverButtonGroup.setVisibility(isVisible ? View.VISIBLE : View.GONE);
bind.nowPlayingTapButton.setVisibility(isVisible ? View.GONE : View.VISIBLE);
bind.innerButtonBottomRight.setVisibility(Preferences.isSyncronizationEnabled() ? View.VISIBLE : View.GONE);
bind.innerButtonBottomRightAlternative.setVisibility(Preferences.isSyncronizationEnabled() ? View.GONE : View.VISIBLE);
if (!isVisible) initTapButtonHideTransition();
}
private void initInnerButton() {
@@ -166,7 +182,7 @@ public class PlayerCoverFragment extends Fragment {
private void setCover(MediaMetadata mediaMetadata) {
CustomGlideRequest.Builder
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null)
.from(requireContext(), mediaMetadata.extras != null ? mediaMetadata.extras.getString("coverArtId") : null, CustomGlideRequest.ResourceType.Song)
.build()
.into(bind.nowPlayingSongCoverImageView);
}

View File

@@ -44,18 +44,25 @@ public class PlayerLyricsFragment extends Fragment {
private void initLyrics() {
playerBottomSheetViewModel.getLiveLyrics().observe(getViewLifecycleOwner(), lyrics -> {
if (bind != null) {
if (lyrics == null || lyrics.trim().equals("")) {
bind.nowPlayingSongLyricsTextView.setVisibility(View.GONE);
bind.emptyDescriptionImageView.setVisibility(View.VISIBLE);
bind.titleEmptyDescriptionLabel.setVisibility(View.VISIBLE);
} else {
bind.nowPlayingSongLyricsTextView.setText(MusicUtil.getReadableLyrics(lyrics));
bind.nowPlayingSongLyricsTextView.setVisibility(View.VISIBLE);
bind.emptyDescriptionImageView.setVisibility(View.GONE);
bind.titleEmptyDescriptionLabel.setVisibility(View.GONE);
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);
}
}
}
});
});
}
}

View File

@@ -25,12 +25,16 @@ import com.cappielloantonio.tempo.ui.adapter.PlayerSongQueueAdapter;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.viewmodel.PlayerBottomSheetViewModel;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.ArrayList;
import java.util.Collections;
import java.util.stream.Collectors;
@UnstableApi
public class PlayerQueueFragment extends Fragment implements ClickCallback {
private static final String TAG = "PlayerQueueFragment";
private InnerFragmentPlayerQueueBinding bind;
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
@@ -54,6 +58,7 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
public void onStart() {
super.onStart();
initializeBrowser();
bindMediaController();
}
@Override
@@ -83,6 +88,18 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
initShuffleButton(mediaBrowser);
initCleanButton(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
@@ -151,6 +168,46 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
}).attachToRecyclerView(bind.playerQueueRecyclerView);
}
private void initShuffleButton(MediaBrowser mediaBrowser) {
bind.playerShuffleQueueFab.setOnClickListener(view -> {
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
int endPosition = playerSongQueueAdapter.getItems().size() - 1;
if (startPosition < endPosition) {
ArrayList<Integer> pool = new ArrayList<>();
for (int i = startPosition; i <= endPosition; i++) {
pool.add(i);
}
while (pool.size() >= 2) {
int fromPosition = (int) (Math.random() * (pool.size()));
int positionA = pool.get(fromPosition);
pool.remove(fromPosition);
int toPosition = (int) (Math.random() * (pool.size()));
int positionB = pool.get(toPosition);
pool.remove(toPosition);
Collections.swap(playerSongQueueAdapter.getItems(), positionA, positionB);
bind.playerQueueRecyclerView.getAdapter().notifyItemMoved(positionA, positionB);
}
MediaManager.shuffle(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
}
});
}
private void initCleanButton(MediaBrowser mediaBrowser) {
bind.playerCleanQueueButton.setOnClickListener(view -> {
int startPosition = mediaBrowser.getCurrentMediaItemIndex() + 1;
int endPosition = playerSongQueueAdapter.getItems().size();
MediaManager.removeRange(mediaBrowserListenableFuture, playerSongQueueAdapter.getItems(), startPosition, endPosition);
bind.playerQueueRecyclerView.getAdapter().notifyItemRangeRemoved(startPosition, endPosition);
});
}
private void updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged();
}

View File

@@ -178,7 +178,7 @@ public class PlaylistCatalogueFragment extends Fragment implements ClickCallback
@Override
public void onPlaylistLongClick(Bundle bundle) {
PlaylistEditorDialog dialog = new PlaylistEditorDialog();
PlaylistEditorDialog dialog = new PlaylistEditorDialog(null);
dialog.setArguments(bundle);
dialog.show(activity.getSupportFragmentManager(), null);
hideKeyboard(requireView());

View File

@@ -167,28 +167,28 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
// Pic top-left
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
.from(requireContext(), songs.size() > 0 ? songs.get(0).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(CustomGlideRequest.CORNER_RADIUS, 0, 0, 0))
.into(bind.playlistCoverImageViewTopLeft);
// Pic top-right
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
.from(requireContext(), songs.size() > 1 ? songs.get(1).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, CustomGlideRequest.CORNER_RADIUS, 0, 0))
.into(bind.playlistCoverImageViewTopRight);
// Pic bottom-left
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
.from(requireContext(), songs.size() > 2 ? songs.get(2).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, 0, 0, CustomGlideRequest.CORNER_RADIUS))
.into(bind.playlistCoverImageViewBottomLeft);
// Pic bottom-right
CustomGlideRequest.Builder
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId())
.from(requireContext(), songs.size() > 3 ? songs.get(3).getCoverArtId() : playlistPageViewModel.getPlaylist().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.transform(new GranularRoundedCorners(0, 0, CustomGlideRequest.CORNER_RADIUS, 0))
.into(bind.playlistCoverImageViewBottomRight);
@@ -200,7 +200,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));

View File

@@ -112,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
}
@@ -242,7 +242,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
}
private boolean isQueryValid(String query) {
Log.d(TAG, "isQueryValid()");
return !query.equals("") && query.trim().length() > 2;
}

View File

@@ -166,7 +166,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
bind.songListRecyclerView.setHasFixedSize(true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true);
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
isLoading = false;

View File

@@ -1,6 +1,9 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -32,8 +35,11 @@ import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.AlbumBottomSheetViewModel;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList;
@@ -43,6 +49,7 @@ import java.util.stream.Collectors;
@UnstableApi
public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel;
private AlbumBottomSheetViewModel albumBottomSheetViewModel;
private AlbumID3 album;
@@ -55,6 +62,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
album = this.requireArguments().getParcelable(Constants.ALBUM_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
albumBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(AlbumBottomSheetViewModel.class);
albumBottomSheetViewModel.setAlbum(album);
@@ -79,7 +87,7 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
private void init(View view) {
ImageView coverAlbum = view.findViewById(R.id.album_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId())
.from(requireContext(), albumBottomSheetViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
.build()
.into(coverAlbum);
@@ -147,8 +155,6 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
}));
TextView downloadAll = view.findViewById(R.id.download_all_text_view);
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
@@ -157,17 +163,21 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
DownloadUtil.getDownloadTracker(requireContext()).download(mediaItems, downloads);
dismissBottomSheet();
});
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
} else {
removeAll.setVisibility(View.GONE);
}
});
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
removeAll.setOnClickListener(v -> {
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
});
initDownloadUI(removeAll);
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> albumBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
@@ -180,6 +190,22 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet();
}));
TextView share = view.findViewById(R.id.share_text_view);
share.setOnClickListener(v -> albumBottomSheetViewModel.shareAlbum().observe(getViewLifecycleOwner(), sharedAlbum -> {
if (sharedAlbum != null) {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedAlbum.getUrl());
clipboardManager.setPrimaryClip(clipData);
refreshShares();
dismissBottomSheet();
} else {
Toast.makeText(requireContext(), getString(R.string.share_unsupported_error), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
@Override
@@ -191,6 +217,16 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
dismiss();
}
private void initDownloadUI(TextView removeAll) {
albumBottomSheetViewModel.getAlbumTracks().observe(getViewLifecycleOwner(), songs -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
if (DownloadUtil.getDownloadTracker(requireContext()).areDownloaded(mediaItems)) {
removeAll.setVisibility(View.VISIBLE);
}
});
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
@@ -198,4 +234,8 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void refreshShares() {
homeViewModel.refreshShares(requireActivity());
}
}

View File

@@ -70,7 +70,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
private void init(View view) {
ImageView coverArtist = view.findViewById(R.id.artist_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId())
.from(requireContext(), artistBottomSheetViewModel.getArtist().getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
.build()
.into(coverArtist);

View File

@@ -0,0 +1,145 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem;
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.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.service.MediaManager;
import com.cappielloantonio.tempo.service.MediaService;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.ui.activity.MainActivity;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
@UnstableApi
public class DownloadedBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private List<Child> songs;
private String groupTitle;
private String groupSubtitle;
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_downloaded_dialog, container, false);
songs = this.requireArguments().getParcelableArrayList(Constants.DOWNLOAD_GROUP);
groupTitle = this.requireArguments().getString(Constants.DOWNLOAD_GROUP_TITLE);
groupSubtitle = this.requireArguments().getString(Constants.DOWNLOAD_GROUP_SUBTITLE);
initUI(view);
init(view);
return view;
}
@Override
public void onStart() {
super.onStart();
initializeMediaBrowser();
}
@Override
public void onStop() {
releaseMediaBrowser();
super.onStop();
}
private void initUI(View view) {
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setVisibility(songs.size() > 1 ? View.VISIBLE : View.GONE);
TextView remove = view.findViewById(R.id.remove_all_text_view);
remove.setText(songs.size() > 1 ? getText(R.string.downloaded_bottom_sheet_remove_all) : getText(R.string.downloaded_bottom_sheet_remove));
}
private void init(View view) {
ImageView coverAlbum = view.findViewById(R.id.group_cover_image_view);
CustomGlideRequest.Builder.from(requireContext(), songs.get(new Random().nextInt(songs.size())).getCoverArtId(), CustomGlideRequest.ResourceType.Unknown).build().into(coverAlbum);
TextView groupTitleView = view.findViewById(R.id.group_title_text_view);
groupTitleView.setText(MusicUtil.getReadableString(this.groupTitle));
groupTitleView.setSelected(true);
TextView groupSubtitleView = view.findViewById(R.id.group_subtitle_text_view);
groupSubtitleView.setText(MusicUtil.getReadableString(this.groupSubtitle));
groupSubtitleView.setSelected(true);
TextView playRandom = view.findViewById(R.id.play_random_text_view);
playRandom.setOnClickListener(v -> {
Collections.shuffle(songs);
MediaManager.startQueue(mediaBrowserListenableFuture, songs, 0);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView playNext = view.findViewById(R.id.play_next_text_view);
playNext.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, true);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView addToQueue = view.findViewById(R.id.add_to_queue_text_view);
addToQueue.setOnClickListener(v -> {
MediaManager.enqueue(mediaBrowserListenableFuture, songs, false);
((MainActivity) requireActivity()).setBottomSheetInPeek(true);
dismissBottomSheet();
});
TextView removeAll = view.findViewById(R.id.remove_all_text_view);
removeAll.setOnClickListener(v -> {
List<MediaItem> mediaItems = MappingUtil.mapDownloads(songs);
List<Download> downloads = songs.stream().map(Download::new).collect(Collectors.toList());
DownloadUtil.getDownloadTracker(requireContext()).remove(mediaItems, downloads);
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void initializeMediaBrowser() {
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
}
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
}

View File

@@ -63,7 +63,7 @@ public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment i
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId())
.from(requireContext(), podcastChannelBottomSheetViewModel.getPodcastChannel().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(coverPodcast);

View File

@@ -65,7 +65,7 @@ public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment i
ImageView coverPodcast = view.findViewById(R.id.podcast_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId())
.from(requireContext(), podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
.build()
.into(coverPodcast);

View File

@@ -0,0 +1,110 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.R;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.subsonic.models.Share;
import com.cappielloantonio.tempo.ui.dialog.ShareUpdateDialog;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.UIUtil;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.ShareBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
@UnstableApi
public class ShareBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private HomeViewModel homeViewModel;
private ShareBottomSheetViewModel shareBottomSheetViewModel;
private Share share;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bottom_sheet_share_dialog, container, false);
share = this.requireArguments().getParcelable(Constants.SHARE_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
shareBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(ShareBottomSheetViewModel.class);
shareBottomSheetViewModel.setShare(share);
init(view);
return view;
}
private void init(View view) {
ImageView shareCover = view.findViewById(R.id.share_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), shareBottomSheetViewModel.getShare().getEntries().get(0).getCoverArtId(), CustomGlideRequest.ResourceType.Unknown)
.build()
.into(shareCover);
TextView shareTitle = view.findViewById(R.id.share_title_text_view);
shareTitle.setText(shareBottomSheetViewModel.getShare().getDescription());
shareTitle.setSelected(true);
TextView shareSubtitle = view.findViewById(R.id.share_subtitle_text_view);
shareSubtitle.setText(requireContext().getString(R.string.share_subtitle_item, UIUtil.getReadableDate(share.getExpires())));
shareSubtitle.setSelected(true);
TextView copyLink = view.findViewById(R.id.copy_link_text_view);
copyLink.setOnClickListener(v -> {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), shareBottomSheetViewModel.getShare().getUrl());
clipboardManager.setPrimaryClip(clipData);
dismissBottomSheet();
});
TextView updateShare = view.findViewById(R.id.update_share_preferences_text_view);
updateShare.setOnClickListener(v -> {
// refreshShares();
showUpdateShareDialog();
dismissBottomSheet();
});
TextView deleteShare = view.findViewById(R.id.delete_share_text_view);
deleteShare.setOnClickListener(v -> {
deleteShare();
refreshShares();
dismissBottomSheet();
});
}
@Override
public void onClick(View v) {
dismissBottomSheet();
}
private void dismissBottomSheet() {
dismiss();
}
private void showUpdateShareDialog() {
ShareUpdateDialog dialog = new ShareUpdateDialog();
dialog.show(requireActivity().getSupportFragmentManager(), null);
}
private void refreshShares() {
homeViewModel.refreshShares(getParentFragment());
}
private void deleteShare() {
shareBottomSheetViewModel.deleteShare();
}
}

View File

@@ -1,6 +1,9 @@
package com.cappielloantonio.tempo.ui.fragment.bottomsheetdialog;
import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,14 +33,15 @@ import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.MusicUtil;
import com.cappielloantonio.tempo.util.Preferences;
import com.cappielloantonio.tempo.viewmodel.HomeViewModel;
import com.cappielloantonio.tempo.viewmodel.SongBottomSheetViewModel;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.google.common.util.concurrent.ListenableFuture;
@UnstableApi
public class SongBottomSheetDialog extends BottomSheetDialogFragment implements View.OnClickListener {
private static final String TAG = "SongBottomSheetDialog";
private HomeViewModel homeViewModel;
private SongBottomSheetViewModel songBottomSheetViewModel;
private Child song;
@@ -50,6 +54,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
song = requireArguments().getParcelable(Constants.TRACK_OBJECT);
homeViewModel = new ViewModelProvider(requireActivity()).get(HomeViewModel.class);
songBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(SongBottomSheetViewModel.class);
songBottomSheetViewModel.setSong(song);
@@ -74,7 +79,7 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void init(View view) {
ImageView coverSong = view.findViewById(R.id.song_cover_image_view);
CustomGlideRequest.Builder
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId())
.from(requireContext(), songBottomSheetViewModel.getSong().getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(coverSong);
@@ -191,6 +196,8 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet();
}));
goToAlbum.setVisibility(songBottomSheetViewModel.getSong().getAlbumId() != null ? View.VISIBLE : View.GONE);
TextView goToArtist = view.findViewById(R.id.go_to_artist_text_view);
goToArtist.setOnClickListener(v -> songBottomSheetViewModel.getArtist().observe(getViewLifecycleOwner(), artist -> {
if (artist != null) {
@@ -202,6 +209,24 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet();
}));
goToArtist.setVisibility(songBottomSheetViewModel.getSong().getArtistId() != null ? View.VISIBLE : View.GONE);
TextView share = view.findViewById(R.id.share_text_view);
share.setOnClickListener(v -> songBottomSheetViewModel.shareTrack().observe(getViewLifecycleOwner(), sharedTrack -> {
if (sharedTrack != null) {
ClipboardManager clipboardManager = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clipData = ClipData.newPlainText(getString(R.string.app_name), sharedTrack.getUrl());
clipboardManager.setPrimaryClip(clipData);
refreshShares();
dismissBottomSheet();
} else {
Toast.makeText(requireContext(), getString(R.string.share_unsupported_error), Toast.LENGTH_SHORT).show();
dismissBottomSheet();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
@Override
@@ -229,4 +254,8 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
private void releaseMediaBrowser() {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void refreshShares() {
homeViewModel.refreshShares(requireActivity());
}
}

View File

@@ -82,6 +82,12 @@ object Constants {
const val DOWNLOAD_TYPE_GENRE = "download_type_genre"
const val DOWNLOAD_TYPE_YEAR = "download_type_year"
const val DOWNLOAD_GROUP = "download_group"
const val DOWNLOAD_GROUP_TITLE = "download_group_title"
const val DOWNLOAD_GROUP_SUBTITLE = "download_group_subtitle"
const val SHARE_OBJECT = "share_object"
const val PLAYABLE_MEDIA_LIMIT = 100
const val PRE_PLAYABLE_MEDIA = 15
}

View File

@@ -1,8 +1,9 @@
package com.cappielloantonio.tempo.util;
import android.app.Notification;
import android.content.Context;
import androidx.media3.common.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
@@ -25,13 +26,14 @@ import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
@UnstableApi
public final class DownloadUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
public static final String DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP = "com.cappielloantonio.tempo.SuccessfulDownload";
public static final String DOWNLOAD_NOTIFICATION_FAILED_GROUP = "com.cappielloantonio.tempo.FailedDownload";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
@@ -184,4 +186,13 @@ public final class DownloadUtil {
return files;
}
public static Notification buildGroupSummaryNotification(Context context, String channelId, String groupId, int icon, String title) {
return new NotificationCompat.Builder(context, channelId)
.setContentTitle(title)
.setSmallIcon(icon)
.setGroup(groupId)
.setGroupSummary(true)
.build();
}
}

View File

@@ -10,6 +10,7 @@ import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
@@ -33,6 +34,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(Child media) {
Uri uri = getUri(media);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(media.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle();
bundle.putString("id", media.getId());
@@ -62,7 +64,7 @@ public class MappingUtil {
bundle.putLong("starred", media.getStarred() != null ? media.getStarred().getTime() : 0);
bundle.putString("albumId", media.getAlbumId());
bundle.putString("artistId", media.getArtistId());
bundle.putString("type", media.getType());
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC);
bundle.putLong("bookmarkPosition", media.getBookmarkPosition() != null ? media.getBookmarkPosition() : 0);
bundle.putInt("originalWidth", media.getOriginalWidth() != null ? media.getOriginalWidth() : 0);
bundle.putInt("originalHeight", media.getOriginalHeight() != null ? media.getOriginalHeight() : 0);
@@ -78,6 +80,7 @@ public class MappingUtil {
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
.setArtist(MusicUtil.getReadableString(media.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle)
.build()
)
@@ -157,6 +160,7 @@ public class MappingUtil {
public static MediaItem mapMediaItem(PodcastEpisode podcastEpisode) {
Uri uri = getUri(podcastEpisode);
Uri artworkUri = Uri.parse(CustomGlideRequest.createUrl(podcastEpisode.getCoverArtId(), Preferences.getImageSize()));
Bundle bundle = new Bundle();
bundle.putString("id", podcastEpisode.getId());
@@ -165,43 +169,29 @@ public class MappingUtil {
bundle.putString("title", podcastEpisode.getTitle());
bundle.putString("album", podcastEpisode.getAlbum());
bundle.putString("artist", podcastEpisode.getArtist());
bundle.putInt("track", podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0);
bundle.putInt("year", podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0);
bundle.putString("genre", podcastEpisode.getGenre());
bundle.putString("coverArtId", podcastEpisode.getCoverArtId());
bundle.putLong("size", podcastEpisode.getSize() != null ? podcastEpisode.getSize() : 0);
bundle.putString("contentType", podcastEpisode.getContentType());
bundle.putString("suffix", podcastEpisode.getSuffix());
bundle.putString("transcodedContentType", podcastEpisode.getTranscodedContentType());
bundle.putString("transcodedSuffix", podcastEpisode.getTranscodedSuffix());
bundle.putInt("duration", podcastEpisode.getDuration() != null ? podcastEpisode.getDuration() : 0);
bundle.putInt("bitrate", podcastEpisode.getBitrate() != null ? podcastEpisode.getBitrate() : 0);
bundle.putString("path", podcastEpisode.getPath());
bundle.putBoolean("isVideo", podcastEpisode.isVideo());
bundle.putInt("userRating", podcastEpisode.getUserRating() != null ? podcastEpisode.getUserRating() : 0);
bundle.putDouble("averageRating", podcastEpisode.getAverageRating() != null ? podcastEpisode.getAverageRating() : 0);
bundle.putLong("playCount", podcastEpisode.getPlayCount() != null ? podcastEpisode.getPlayCount() : 0);
bundle.putInt("discNumber", podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0);
bundle.putLong("created", podcastEpisode.getCreated() != null ? podcastEpisode.getCreated().getTime() : 0);
bundle.putLong("starred", podcastEpisode.getStarred() != null ? podcastEpisode.getStarred().getTime() : 0);
bundle.putString("albumId", podcastEpisode.getAlbumId());
bundle.putString("artistId", podcastEpisode.getArtistId());
bundle.putString("type", podcastEpisode.getType());
bundle.putLong("bookmarkPosition", podcastEpisode.getBookmarkPosition() != null ? podcastEpisode.getBookmarkPosition() : 0);
bundle.putInt("originalWidth", podcastEpisode.getOriginalWidth() != null ? podcastEpisode.getOriginalWidth() : 0);
bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0);
bundle.putString("description", podcastEpisode.getDescription());
bundle.putString("type", Constants.MEDIA_TYPE_PODCAST);
bundle.putString("uri", uri.toString());
return new MediaItem.Builder()
MediaItem item = new MediaItem.Builder()
.setMediaId(podcastEpisode.getId())
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
.setTrackNumber(podcastEpisode.getTrack() != null ? podcastEpisode.getTrack() : 0)
.setDiscNumber(podcastEpisode.getDiscNumber() != null ? podcastEpisode.getDiscNumber() : 0)
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
.setArtworkUri(artworkUri)
.setExtras(bundle)
.build()
)
@@ -211,9 +201,17 @@ public class MappingUtil {
.setExtras(bundle)
.build()
)
/* .setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(0)
.setEndPositionMs(podcastEpisode.getDuration() * 1000)
.build()
) */
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.setUri(uri)
.build();
return item;
}
private static Uri getUri(Child media) {

View File

@@ -14,6 +14,8 @@ import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import java.text.CharacterIterator;
import java.text.StringCharacterIterator;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -48,6 +50,8 @@ public class MusicUtil {
uri.append("&maxBitRate=").append(getBitratePreference());
if (!Preferences.isServerPrioritized())
uri.append("&format=").append(getTranscodingFormatPreference());
if (Preferences.askForEstimateContentLength())
uri.append("&estimateContentLength=true");
uri.append("&id=").append(id);
@@ -211,6 +215,27 @@ public class MusicUtil {
return readableStrings;
}
public static String getReadableByteCount(long bytes) {
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
if (absB < 1024) {
return bytes + " B";
}
long value = absB;
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
value >>= 10;
ci.next();
}
value *= Long.signum(bytes);
return String.format("%.1f %ciB", value / 1024.0, ci.current());
}
public static String passwordHexEncoding(String plainPassword) {
return "enc:" + plainPassword.chars().mapToObj(Integer::toHexString).collect(Collectors.joining());
}

View File

@@ -39,6 +39,10 @@ object Preferences {
private const val AUDIO_TRANSCODE_DOWNLOAD_PRIORITY = "audio_transcode_download_priority"
private const val MAX_BITRATE_DOWNLOAD = "max_bitrate_download"
private const val AUDIO_TRANSCODE_FORMAT_DOWNLOAD = "audio_transcode_format_download"
private const val SHARE = "share"
private const val SCROBBLING = "scrobbling"
private const val ESTIMATE_CONTENT_LENGTH = "estimate_content_length"
private const val BUFFERING_STRATEGY = "buffering_strategy"
@JvmStatic
fun getServer(): String? {
@@ -313,4 +317,25 @@ object Preferences {
fun getAudioTranscodeFormatTranscodedDownload(): String {
return App.getInstance().preferences.getString(AUDIO_TRANSCODE_FORMAT_DOWNLOAD, "raw")!!
}
@JvmStatic
fun isSharingEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(SHARE, false)
}
@JvmStatic
fun isScrobblingEnabled(): Boolean {
return App.getInstance().preferences.getBoolean(SCROBBLING, true)
}
@JvmStatic
fun askForEstimateContentLength(): Boolean {
return App.getInstance().preferences.getBoolean(ESTIMATE_CONTENT_LENGTH, false)
}
@JvmStatic
fun getBufferingStrategy(): Double {
return App.getInstance().preferences.getString(BUFFERING_STRATEGY, "1")!!.toDouble()
}
}

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.util;
import androidx.annotation.OptIn;
import androidx.media3.common.MediaItem;
import androidx.media3.common.Metadata;
import androidx.media3.common.Tracks;
import androidx.media3.common.util.UnstableApi;
@@ -61,6 +62,9 @@ public class ReplayGainUtil {
}
}
if (gains.size() == 0) gains.add(0, new ReplayGain());
if (gains.size() == 1) gains.add(1, new ReplayGain());
return gains;
}
@@ -105,13 +109,67 @@ public class ReplayGainUtil {
}
private static void applyReplayGain(ExoPlayer player, List<ReplayGain> gains) {
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains.size() == 0) {
setReplayGain(player, 0f);
} else if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
setReplayGain(player, gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(0).getAlbumGain());
} else if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
setReplayGain(player, gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(0).getTrackGain());
if (Objects.equals(Preferences.getReplayGainMode(), "disabled") || gains == null || gains.isEmpty()) {
setNoReplayGain(player);
return;
}
if (Objects.equals(Preferences.getReplayGainMode(), "auto")) {
if (areTracksConsecutive(player)) {
setAutoReplayGain(player, gains);
} else {
setTrackReplayGain(player, gains);
}
return;
}
if (Objects.equals(Preferences.getReplayGainMode(), "track")) {
setTrackReplayGain(player, gains);
return;
}
if (Objects.equals(Preferences.getReplayGainMode(), "album")) {
setAlbumReplayGain(player, gains);
return;
}
setNoReplayGain(player);
}
private static void setNoReplayGain(ExoPlayer player) {
setReplayGain(player, 0f);
}
private static void setTrackReplayGain(ExoPlayer player, List<ReplayGain> gains) {
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
setReplayGain(player, trackGain != 0f ? trackGain : 0f);
}
private static void setAlbumReplayGain(ExoPlayer player, List<ReplayGain> gains) {
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
setReplayGain(player, albumGain != 0f ? albumGain : 0f);
}
private static void setAutoReplayGain(ExoPlayer player, List<ReplayGain> gains) {
float albumGain = gains.get(0).getAlbumGain() != 0f ? gains.get(0).getAlbumGain() : gains.get(1).getAlbumGain();
float trackGain = gains.get(0).getTrackGain() != 0f ? gains.get(0).getTrackGain() : gains.get(1).getTrackGain();
setReplayGain(player, albumGain != 0f ? albumGain : trackGain);
}
private static boolean areTracksConsecutive(ExoPlayer player) {
MediaItem currentMediaItem = player.getCurrentMediaItem();
int currentMediaItemIndex = player.getCurrentMediaItemIndex();
MediaItem pastMediaItem = currentMediaItemIndex > 0 ? player.getMediaItemAt(currentMediaItemIndex - 1) : null;
return currentMediaItem != null &&
pastMediaItem != null &&
pastMediaItem.mediaMetadata.albumTitle != null &&
currentMediaItem.mediaMetadata.albumTitle != null &&
pastMediaItem.mediaMetadata.albumTitle.toString().equals(currentMediaItem.mediaMetadata.albumTitle.toString());
}
private static void setReplayGain(ExoPlayer player, float gain) {

View File

@@ -14,7 +14,9 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -84,4 +86,9 @@ public class UIUtil {
return map;
}
public static String getReadableDate(Date date) {
SimpleDateFormat formatter = new SimpleDateFormat("dd MMM, yyyy", Locale.getDefault());
return formatter.format(date);
}
}

View File

@@ -11,9 +11,11 @@ import com.cappielloantonio.tempo.interfaces.StarCallback;
import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
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.NetworkUtil;
import java.util.Date;
@@ -23,6 +25,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private AlbumID3 album;
@@ -32,6 +35,7 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
}
public AlbumID3 getAlbum() {
@@ -66,6 +70,10 @@ public class AlbumBottomSheetViewModel extends AndroidViewModel {
}
}
public MutableLiveData<Share> shareAlbum() {
return sharingRepository.createShare(album.getId(), album.getName(), null);
}
private void removeFavoriteOffline() {
favoriteRepository.starLater(null, album.getId(), null, false);
album.setStarred(null);

View File

@@ -61,9 +61,8 @@ public class AlbumCatalogueViewModel extends AndroidViewModel {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null) {
List<AlbumID3> albumList = new ArrayList<>();
albumList.addAll(response.body().getSubsonicResponse().getAlbumList2().getAlbums());
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
List<AlbumID3> albumList = new ArrayList<>(response.body().getSubsonicResponse().getAlbumList2().getAlbums());
callback.onLoadMedia(albumList);
}
}

View File

@@ -1,6 +1,7 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
@@ -11,7 +12,6 @@ import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.model.DownloadStack;
import com.cappielloantonio.tempo.repository.DownloadRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.util.Constants;
import com.cappielloantonio.tempo.util.Preferences;
import java.util.ArrayList;

View File

@@ -15,10 +15,12 @@ import com.cappielloantonio.tempo.repository.AlbumRepository;
import com.cappielloantonio.tempo.repository.ArtistRepository;
import com.cappielloantonio.tempo.repository.ChronologyRepository;
import com.cappielloantonio.tempo.repository.FavoriteRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.repository.SongRepository;
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
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 java.util.ArrayList;
@@ -36,6 +38,7 @@ public class HomeViewModel extends AndroidViewModel {
private final ArtistRepository artistRepository;
private final ChronologyRepository chronologyRepository;
private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
private final MutableLiveData<List<AlbumID3>> newReleasedAlbum = new MutableLiveData<>(null);
@@ -54,6 +57,8 @@ public class HomeViewModel extends AndroidViewModel {
private final MutableLiveData<List<Child>> mediaInstantMix = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> artistInstantMix = new MutableLiveData<>(null);
private final MutableLiveData<List<Child>> artistBestOf = new MutableLiveData<>(null);
private final MutableLiveData<List<Share>> shares = new MutableLiveData<>(null);
public HomeViewModel(@NonNull Application application) {
super(application);
@@ -63,6 +68,7 @@ public class HomeViewModel extends AndroidViewModel {
artistRepository = new ArtistRepository();
chronologyRepository = new ChronologyRepository();
favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
setOfflineFavorite();
}
@@ -204,6 +210,14 @@ public class HomeViewModel extends AndroidViewModel {
return artistBestOf;
}
public LiveData<List<Share>> getShares(LifecycleOwner owner) {
if (shares.getValue() == null) {
sharingRepository.getShares().observe(owner, shares::postValue);
}
return shares;
}
public LiveData<List<Child>> getAllStarredTracks() {
return songRepository.getStarredSongs(false, -1);
}
@@ -248,6 +262,10 @@ public class HomeViewModel extends AndroidViewModel {
albumRepository.getAlbums("recent", 20, null, null).observe(owner, recentlyPlayedAlbumSample::postValue);
}
public void refreshShares(LifecycleOwner owner) {
sharingRepository.getShares().observe(owner, this.shares::postValue);
}
public void setOfflineFavorite() {
ArrayList<Favorite> favorites = getFavorites();
ArrayList<Favorite> favoritesToSave = getFavoritesToSave(favorites);

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