51 Commits
3.5.4 ... 3.5.7

Author SHA1 Message Date
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
121 changed files with 3068 additions and 384 deletions

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

@@ -0,0 +1,33 @@
---
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.

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

@@ -0,0 +1,39 @@
---
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 ```).
**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

@@ -28,8 +28,8 @@ android {
tempo {
dimension "default"
applicationId 'com.cappielloantonio.tempo'
versionCode 19
versionName '3.5.4'
versionCode 22
versionName '3.5.7'
}
notquitemy {
@@ -72,8 +72,8 @@ 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.1'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.7.2'
implementation 'androidx.recyclerview:recyclerview:1.3.1'
implementation 'androidx.room:room-runtime:2.5.2'
implementation 'androidx.core:core-splashscreen:1.0.1'

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

@@ -249,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,7 +95,7 @@ public class PlaylistRepository {
.enqueue(new Callback<ApiResponse>() {
@Override
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
Log.d("createPlaylist", "onResponse: ");
}
@Override

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,97 @@
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));
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
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

@@ -204,6 +204,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(() -> {

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

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

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

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.getOriginalImageUrl(), 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

@@ -59,13 +59,12 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
}
if (showCoverArt) CustomGlideRequest.Builder
.from(holder.itemView.getContext(), song.getCoverArtId())
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
.build()
.into(holder.item.songCoverImageView);
if (showCoverArt) holder.item.trackNumberTextView.setVisibility(View.INVISIBLE);
if (!showCoverArt) holder.item.songCoverImageView.setVisibility(View.INVISIBLE);
holder.item.trackNumberTextView.setVisibility(showCoverArt ? View.INVISIBLE : View.VISIBLE);
holder.item.songCoverImageView.setVisibility(showCoverArt ? View.VISIBLE : View.INVISIBLE);
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

@@ -2,8 +2,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;
@@ -18,6 +22,7 @@ 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 java.util.Collections;
@@ -99,6 +104,16 @@ public class PlaylistEditorDialog extends DialogFragment {
playlistEditorViewModel.deletePlaylist();
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() {

View File

@@ -0,0 +1,137 @@
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.datepicker.MaterialPickerOnPositiveButtonClickListener;
import java.util.Calendar;
import java.util.Date;
import java.util.Objects;
public class ShareUpdateDialog extends DialogFragment {
private static final String TAG = "ShareUpdateDialog";
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());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.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());
return builder.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

@@ -0,0 +1,132 @@
package com.cappielloantonio.tempo.ui.dialog;
import android.app.AlertDialog;
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;
public class TrackInfoDialog extends DialogFragment {
private static final String TAG = "TrackInfoDialog";
private DialogTrackInfoBinding bind;
private MediaMetadata mediaMetadata;
public TrackInfoDialog(MediaMetadata mediaMetadata) {
this.mediaMetadata = mediaMetadata;
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
bind = DialogTrackInfoBinding.inflate(getLayoutInflater());
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setView(bind.getRoot())
.setPositiveButton(R.string.track_info_dialog_positive_button, (dialog, id) -> dialog.cancel());
return builder.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);
}

View File

@@ -119,7 +119,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);

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,7 +1,10 @@
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;
@@ -33,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;
@@ -40,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;
@@ -76,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;
@@ -111,6 +117,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initYearSongView();
initRecentAddedAlbumView();
initGridView();
initSharesView();
}
@Override
@@ -120,6 +127,12 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
initializeMediaBrowser();
}
@Override
public void onResume() {
super.onResume();
refreshSharesView();
}
@Override
public void onStop() {
releaseMediaBrowser();
@@ -227,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() {
@@ -289,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);
}
@@ -310,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);
}
@@ -332,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);
}
@@ -354,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);
}
@@ -377,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);
}
});
}
});
}
@@ -396,12 +431,16 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
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);
}
@@ -427,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);
}
@@ -458,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);
}
@@ -490,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);
}
@@ -522,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);
}
@@ -544,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);
@@ -567,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);
}
@@ -589,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);
}
@@ -603,6 +667,50 @@ 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 (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);
@@ -702,4 +810,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

@@ -161,7 +161,7 @@ public class PlayerBottomSheetFragment extends Fragment {
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);
}
@@ -240,6 +240,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,12 +29,14 @@ 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;
@@ -51,10 +53,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 +70,10 @@ public class PlayerControllerFragment extends Fragment {
playerBottomSheetViewModel = new ViewModelProvider(requireActivity()).get(PlayerBottomSheetViewModel.class);
init();
initQuickActionView();
initCoverLyricsSlideView();
initMediaListenable();
initArtistLabelButton();
initTranscodingInfo();
return view;
}
@@ -106,10 +106,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() {
@@ -160,9 +169,7 @@ public class PlayerControllerFragment extends Fragment {
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 +181,10 @@ public class PlayerControllerFragment extends Fragment {
}
}
String transcodingExtension = MusicUtil.getTranscodingFormatPreference();
String transcodingBitrate = Integer.parseInt(MusicUtil.getBitratePreference()) != 0
? Integer.parseInt(MusicUtil.getBitratePreference()) + "kbps"
: "Original";
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 (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) {
@@ -305,13 +284,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

@@ -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,17 @@ public class PlayerQueueFragment extends Fragment implements ClickCallback {
MediaBrowser.releaseFuture(mediaBrowserListenableFuture);
}
private void bindMediaController() {
mediaBrowserListenableFuture.addListener(() -> {
try {
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
initShuffleButton(mediaBrowser);
} catch (Exception exception) {
exception.printStackTrace();
}
}, MoreExecutors.directExecutor());
}
private void setMediaBrowserListenableFuture() {
playerSongQueueAdapter.setMediaBrowserListenableFuture(mediaBrowserListenableFuture);
}
@@ -151,6 +167,36 @@ 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 updateNowPlayingItem() {
playerSongQueueAdapter.notifyDataSetChanged();
}

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

View File

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

@@ -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,7 +35,9 @@ 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.common.util.concurrent.ListenableFuture;
@@ -43,6 +48,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 +61,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 +86,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 +154,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 +162,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 +189,19 @@ 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();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
@Override
@@ -191,6 +213,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 +230,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);
@@ -202,6 +207,19 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
dismissBottomSheet();
}));
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();
}
}));
share.setVisibility(Preferences.isSharingEnabled() ? View.VISIBLE : View.GONE);
}
@Override
@@ -229,4 +247,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

@@ -196,7 +196,7 @@ public class MappingUtil {
bundle.putInt("originalHeight", podcastEpisode.getOriginalHeight() != null ? podcastEpisode.getOriginalHeight() : 0);
bundle.putString("uri", uri.toString());
return new MediaItem.Builder()
MediaItem item = new MediaItem.Builder()
.setMediaId(podcastEpisode.getId())
.setMediaMetadata(
new MediaMetadata.Builder()
@@ -216,9 +216,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,8 @@ 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 ESTIMATE_CONTENT_LENGTH = "estimate_content_length"
@JvmStatic
fun getServer(): String? {
@@ -313,4 +315,14 @@ 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 askForEstimateContentLength(): Boolean {
return App.getInstance().preferences.getBoolean(ESTIMATE_CONTENT_LENGTH, false)
}
}

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

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

View File

@@ -23,8 +23,8 @@ public class IndexViewModel extends AndroidViewModel {
directoryRepository = new DirectoryRepository();
}
public MutableLiveData<Indexes> getIndexes() {
return directoryRepository.getIndexes(null, null);
public MutableLiveData<Indexes> getIndexes(String musicFolderId) {
return directoryRepository.getIndexes(musicFolderId, null);
}
public String getMusicFolderName() {

View File

@@ -8,8 +8,10 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.repository.PlaylistRepository;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Child;
import com.cappielloantonio.tempo.subsonic.models.Playlist;
import com.cappielloantonio.tempo.subsonic.models.Share;
import java.util.ArrayList;
import java.util.Collections;
@@ -20,6 +22,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
private static final String TAG = "PlaylistEditorViewModel";
private final PlaylistRepository playlistRepository;
private final SharingRepository sharingRepository;
private Child toAdd;
private Playlist toEdit;
@@ -30,6 +33,7 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
super(application);
playlistRepository = new PlaylistRepository();
sharingRepository = new SharingRepository();
}
public void createPlaylist(String name) {
@@ -92,4 +96,8 @@ public class PlaylistEditorViewModel extends AndroidViewModel {
return ids;
}
public MutableLiveData<Share> sharePlaylist() {
return sharingRepository.createShare(toEdit.getId(), toEdit.getName(), null);
}
}

View File

@@ -0,0 +1,37 @@
package com.cappielloantonio.tempo.viewmodel;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import com.cappielloantonio.tempo.repository.SharingRepository;
import com.cappielloantonio.tempo.subsonic.models.Share;
public class ShareBottomSheetViewModel extends AndroidViewModel {
private final SharingRepository sharingRepository;
private Share share;
public ShareBottomSheetViewModel(@NonNull Application application) {
super(application);
sharingRepository = new SharingRepository();
}
public Share getShare() {
return share;
}
public void setShare(Share share) {
this.share = share;
}
public void updateShare(String description, long expires) {
sharingRepository.updateShare(share.getId(), description, expires);
}
public void deleteShare() {
sharingRepository.deleteShare(share.getId());
}
}

View File

@@ -15,10 +15,12 @@ import com.cappielloantonio.tempo.model.Download;
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.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.DownloadUtil;
import com.cappielloantonio.tempo.util.MappingUtil;
import com.cappielloantonio.tempo.util.NetworkUtil;
@@ -34,6 +36,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
private final AlbumRepository albumRepository;
private final ArtistRepository artistRepository;
private final FavoriteRepository favoriteRepository;
private final SharingRepository sharingRepository;
private Child song;
@@ -46,6 +49,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
albumRepository = new AlbumRepository();
artistRepository = new ArtistRepository();
favoriteRepository = new FavoriteRepository();
sharingRepository = new SharingRepository();
}
public Child getSong() {
@@ -128,4 +132,8 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
return instantMix;
}
public MutableLiveData<Share> shareTrack() {
return sharingRepository.createShare(song.getId(), song.getTitle(), null);
}
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M440,680L520,680L520,440L440,440L440,680ZM480,360Q497,360 508.5,348.5Q520,337 520,320Q520,303 508.5,291.5Q497,280 480,280Q463,280 451.5,291.5Q440,303 440,320Q440,337 451.5,348.5Q463,360 480,360ZM480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM480,800Q614,800 707,707Q800,614 800,480Q800,346 707,253Q614,160 480,160Q346,160 253,253Q160,346 160,480Q160,614 253,707Q346,800 480,800ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z"/>
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 12 5 C 8.136 5 5 8.136 5 12 C 5 15.864 8.136 19 12 19 C 15.864 19 19 15.864 19 12 C 19 8.136 15.864 5 12 5 Z M 12 15.15 C 10.257 15.15 8.85 13.743 8.85 12 C 8.85 10.257 10.257 8.85 12 8.85 C 13.743 8.85 15.15 10.257 15.15 12 C 15.15 13.743 13.743 15.15 12 15.15 Z M 12 11.3 C 11.615 11.3 11.3 11.615 11.3 12 C 11.3 12.385 11.615 12.7 12 12.7 C 12.385 12.7 12.7 12.385 12.7 12 C 12.7 11.615 12.385 11.3 12 11.3 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:fillType="evenOdd"
android:pathData="M 14.531 12.601 C 15.273 13.096 15.793 13.767 15.793 14.661 L 15.793 16.258 L 17.961 16.258 L 17.961 14.661 C 17.961 13.501 16.026 12.814 14.531 12.601 Z M 8.207 9.871 C 8.207 9.307 8.435 8.765 8.842 8.366 C 9.248 7.967 9.8 7.742 10.374 7.742 C 10.949 7.742 11.501 7.967 11.907 8.366 C 12.313 8.765 12.542 9.307 12.542 9.871 C 12.542 10.435 12.313 10.978 11.907 11.376 C 11.501 11.775 10.949 12 10.374 12 C 9.8 12 9.248 11.775 8.842 11.376 C 8.435 10.978 8.207 10.435 8.207 9.871 M 13.626 12 C 14.823 12 15.793 11.047 15.793 9.871 C 15.793 8.695 14.823 7.742 13.626 7.742 C 13.371 7.742 13.133 7.795 12.905 7.87 C 13.355 8.418 13.626 9.115 13.626 9.871 C 13.626 10.627 13.355 11.324 12.905 11.872 C 13.133 11.947 13.371 12 13.626 12 Z M 10.374 12.532 C 8.927 12.532 6.039 13.245 6.039 14.661 L 6.039 16.258 L 14.71 16.258 L 14.71 14.661 C 14.71 13.245 11.821 12.532 10.374 12.532 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 10.834 7.335 L 7.335 7.335 C 6.694 7.335 6.175 7.86 6.175 8.502 L 6.169 15.498 C 6.169 16.14 6.694 16.665 7.335 16.665 L 16.665 16.665 C 17.306 16.665 17.831 16.14 17.831 15.498 L 17.831 9.668 C 17.831 9.026 17.306 8.502 16.665 8.502 L 12 8.502 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 10.834 7.335 L 7.335 7.335 C 6.694 7.335 6.175 7.86 6.175 8.502 L 6.169 15.498 C 6.169 16.14 6.694 16.665 7.335 16.665 L 16.665 16.665 C 17.306 16.665 17.831 16.14 17.831 15.498 L 17.831 9.668 C 17.831 9.026 17.306 8.502 16.665 8.502 L 12 8.502 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 13.593 8.061 L 5.946 8.061 L 5.946 9.329 L 13.593 9.329 Z M 13.593 10.598 L 5.946 10.598 L 5.946 11.866 L 13.593 11.866 Z M 5.946 14.403 L 11.044 14.403 L 11.044 13.134 L 5.946 13.134 Z M 14.867 8.061 L 14.867 13.249 C 14.669 13.179 14.453 13.134 14.23 13.134 C 13.172 13.134 12.318 13.984 12.318 15.037 C 12.318 16.09 13.172 16.939 14.23 16.939 C 15.288 16.939 16.141 16.09 16.141 15.037 L 16.141 9.329 L 18.053 9.329 L 18.053 8.061 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 13.4 12 C 13.4 12.518 13.12 12.966 12.7 13.204 L 12.7 19 L 11.3 19 L 11.3 13.204 C 10.88 12.959 10.6 12.518 10.6 12 C 10.6 11.23 11.23 10.6 12 10.6 C 12.77 10.6 13.4 11.23 13.4 12 Z M 12 7.8 C 9.683 7.8 7.8 9.683 7.8 12 C 7.8 13.218 8.325 14.317 9.158 15.08 L 10.152 14.086 C 9.571 13.575 9.2 12.833 9.2 12 C 9.2 10.453 10.453 9.2 12 9.2 C 13.547 9.2 14.8 10.453 14.8 12 C 14.8 12.833 14.429 13.575 13.848 14.086 L 14.842 15.08 C 15.675 14.317 16.2 13.218 16.2 12 C 16.2 9.683 14.317 7.8 12 7.8 Z M 12 5 C 8.136 5 5 8.136 5 12 C 5 13.995 5.84 15.787 7.177 17.068 L 8.171 16.074 C 7.086 15.052 6.4 13.603 6.4 12 C 6.4 8.913 8.913 6.4 12 6.4 C 15.087 6.4 17.6 8.913 17.6 12 C 17.6 13.603 16.914 15.052 15.829 16.074 L 16.823 17.068 C 18.16 15.787 19 13.995 19 12 C 19 8.136 15.864 5 12 5 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 7.119 8.519 C 6.712 8.675 6.428 9.087 6.428 9.55 L 6.428 16.237 C 6.428 16.85 6.924 17.351 7.542 17.351 L 16.458 17.351 C 17.076 17.351 17.572 16.85 17.572 16.237 L 17.572 9.55 C 17.572 8.931 17.076 8.435 16.458 8.435 L 9.938 8.435 L 14.541 6.574 L 14.162 5.649 Z M 9.214 16.237 C 8.289 16.237 7.542 15.49 7.542 14.565 C 7.542 13.64 8.289 12.893 9.214 12.893 C 10.139 12.893 10.886 13.64 10.886 14.565 C 10.886 15.49 10.139 16.237 9.214 16.237 Z M 16.458 11.779 L 15.343 11.779 L 15.343 10.664 L 14.229 10.664 L 14.229 11.779 L 7.542 11.779 L 7.542 9.55 L 16.458 9.55 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:name="vector"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:name="path"
android:fillColor="?attr/colorSurfaceVariant"
android:pathData="M 0 0 L 24 0 L 24 24 L 0 24 Z"
android:strokeWidth="1" />
<path
android:name="path_1"
android:fillColor="?attr/colorOnSurfaceVariant"
android:pathData="M 12.15 6.255 L 12.15 12.989 C 11.775 12.772 11.342 12.638 10.878 12.638 C 9.472 12.638 8.334 13.781 8.334 15.192 C 8.334 16.602 9.472 17.745 10.878 17.745 C 12.284 17.745 13.422 16.602 13.422 15.192 L 13.422 8.808 L 15.966 8.808 L 15.966 6.255 Z"
android:strokeWidth="1" />
</vector>

View File

@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:autoMirrored="true">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M624,720Q574,720 539,685Q504,650 504,600Q504,550 539,515Q574,480 624,480Q636,480 648.5,482.5Q661,485 672,490L672,192L864,192L864,264L744,264L744,600Q744,650 709,685Q674,720 624,720ZM144,552L144,480L432,480L432,552L144,552ZM144,408L144,336L576,336L576,408L144,408ZM144,264L144,192L576,192L576,264L144,264Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M696,864Q646,864 611,829Q576,794 576,744Q576,736 577,729.5Q578,723 580,715L342,570Q327,586 306.65,593Q286.29,600 264,600Q214,600 179,565Q144,530 144,480Q144,430 179,395Q214,360 264,360Q286,360 306.5,367.5Q327,375 342,390L580,245Q578,237 577,230.5Q576,224 576,216Q576,166 611,131Q646,96 696,96Q746,96 781,131Q816,166 816,216Q816,266 781,301Q746,336 696,336Q673.71,336 653.35,329Q633,322 618,306L380,451Q382,459 383,465.5Q384,472 384,480Q384,488 383,494.5Q382,501 380,509L618,654Q633,637 653.35,630.5Q673.71,624 696,624Q746,624 781,659Q816,694 816,744Q816,794 781,829Q746,864 696,864Z"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@color/titleTextColor"
android:pathData="M419,880Q391,880 366.5,868Q342,856 325,834L107,557L126,537Q146,516 174,512Q202,508 226,523L300,568L300,240Q300,223 311.5,211.5Q323,200 340,200Q357,200 369,211.5Q381,223 381,240L381,712L284,652L388,785Q394,792 402,796Q410,800 419,800L640,800Q673,800 696.5,776.5Q720,753 720,720L720,560Q720,543 708.5,531.5Q697,520 680,520L461,520L461,440L680,440Q730,440 765,475Q800,510 800,560L800,720Q800,786 753,833Q706,880 640,880L419,880ZM167,340Q154,318 147,292.5Q140,267 140,240Q140,157 198.5,98.5Q257,40 340,40Q423,40 481.5,98.5Q540,157 540,240Q540,267 533,292.5Q526,318 513,340L444,300Q452,286 456,271.5Q460,257 460,240Q460,190 425,155Q390,120 340,120Q290,120 255,155Q220,190 220,240Q220,257 224,271.5Q228,286 236,300L167,340ZM502,620L502,620L502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620Q502,620 502,620Q502,620 502,620L502,620L502,620L502,620Z"/>
</vector>

View File

@@ -157,6 +157,7 @@
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:visibility="gone"
android:text="@string/album_bottom_sheet_remove_all" />
<TextView
@@ -171,5 +172,19 @@
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/album_bottom_sheet_go_to_artist" />
<TextView
android:id="@+id/share_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/album_bottom_sheet_share"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp">
<!-- Header -->
<ImageView
android:id="@+id/group_cover_image_view"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_margin="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/group_title_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:focusable="true"
android:focusableInTouchMode="true"
android:marqueeRepeatLimit="marquee_forever"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:scrollHorizontally="true"
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintVertical_chainStyle="packed"
app:layout_constraintBottom_toTopOf="@+id/group_subtitle_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/group_cover_image_view"
app:layout_constraintTop_toTopOf="@+id/group_cover_image_view" />
<TextView
android:id="@+id/group_subtitle_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/label_placeholder"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/group_cover_image_view"
app:layout_constraintTop_toBottomOf="@+id/group_title_text_view"
app:layout_constraintBottom_toBottomOf="@+id/group_cover_image_view"/>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/option_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/play_random_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/downloaded_bottom_sheet_shuffle" />
<TextView
android:id="@+id/play_next_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/downloaded_bottom_sheet_play_next" />
<TextView
android:id="@+id/add_to_queue_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/downloaded_bottom_sheet_add_to_queue" />
<TextView
android:id="@+id/remove_all_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/downloaded_bottom_sheet_remove_all" />
</LinearLayout>
</LinearLayout>

View File

@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="12dp"
android:clipChildren="false">
<!-- Header -->
<ImageView
android:id="@+id/share_cover_image_view"
android:layout_width="54dp"
android:layout_height="54dp"
android:layout_margin="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/share_title_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:singleLine="true"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toTopOf="@+id/share_subtitle_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/share_cover_image_view"
app:layout_constraintTop_toTopOf="@+id/share_cover_image_view"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/share_subtitle_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:paddingEnd="12dp"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="@+id/share_cover_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/share_cover_image_view"
app:layout_constraintTop_toBottomOf="@+id/share_title_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:id="@+id/option_linear_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent">
<TextView
android:id="@+id/copy_link_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/share_bottom_sheet_copy_link" />
<TextView
android:id="@+id/update_share_preferences_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/share_bottom_sheet_update" />
<TextView
android:id="@+id/delete_share_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/share_bottom_sheet_delete" />
</LinearLayout>
</LinearLayout>

View File

@@ -195,5 +195,18 @@
android:paddingBottom="12dp"
android:text="@string/song_bottom_sheet_go_to_artist" />
<TextView
android:id="@+id/share_text_view"
style="@style/LabelMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:paddingStart="20dp"
android:paddingTop="12dp"
android:paddingEnd="20dp"
android:paddingBottom="12dp"
android:text="@string/song_bottom_sheet_share"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>

View File

@@ -4,26 +4,47 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:orientation="horizontal"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
android:layout_marginStart="24dp"
android:layout_marginEnd="12dp">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/playlist_name_text_view"
android:layout_width="match_parent"
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:hint="@string/playlist_editor_dialog_hint_name"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
android:layout_weight="1"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/playlist_name_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/playlist_editor_dialog_hint_name"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<FrameLayout
android:id="@+id/playlist_share_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="12dp"
android:layout_marginTop="10dp">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/ic_share"
android:foreground="?android:attr/selectableItemBackgroundBorderless" />
</FrameLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/playlist_song_recycler_view"

View File

@@ -0,0 +1,51 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/share_description_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/share_update_dialog_hint_description"
android:inputType="textNoSuggestions"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textColorHint="?android:textColorHint"
app:endIconMode="clear_text"
app:endIconTint="?android:textColorSecondary"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/share_expiration_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:longClickable="false"
android:textIsSelectable="false"
android:hint="@string/share_update_dialog_hint_expiration_date"
android:inputType="textShortMessage"
android:textCursorDrawable="@null" />
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>

View File

@@ -0,0 +1,447 @@
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp">
<ImageView
android:id="@+id/track_cover_info_image_view"
android:layout_width="52dp"
android:layout_height="52dp"
android:layout_gravity="center"
android:layout_marginEnd="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/trak_title_info_text_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/trak_title_info_text_view"
style="@style/LabelMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toTopOf="@+id/trak_artist_info_text_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/track_cover_info_image_view"
app:layout_constraintTop_toTopOf="@+id/track_cover_info_image_view"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="@+id/trak_artist_info_text_view"
style="@style/LabelSmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/label_placeholder"
app:layout_constraintBottom_toBottomOf="@+id/track_cover_info_image_view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/trak_title_info_text_view"
app:layout_constraintTop_toBottomOf="@+id/trak_title_info_text_view" />
</androidx.constraintlayout.widget.ConstraintLayout>
<TextView
android:id="@+id/trak_transcoding_info_text_view"
style="@style/TitleMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp" />
<LinearLayout
android:id="@+id/title_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<TextView
android:id="@+id/title_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_title" />
<TextView
android:id="@+id/title_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/album_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/album_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_album" />
<TextView
android:id="@+id/album_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/artist_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/artist_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_artist" />
<TextView
android:id="@+id/artist_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/track_number_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/track_number_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_track_number" />
<TextView
android:id="@+id/track_number_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/year_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/year_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_year" />
<TextView
android:id="@+id/year_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/genre_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/genre_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_genre" />
<TextView
android:id="@+id/genre_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/size_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/size_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_size" />
<TextView
android:id="@+id/size_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/content_type_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/content_type_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_content_type" />
<TextView
android:id="@+id/content_type_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/suffix_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/suffix_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_suffix" />
<TextView
android:id="@+id/suffix_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/transcoded_content_type_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/transcoded_content_type_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_transcoded_content_type" />
<TextView
android:id="@+id/transcoded_content_type_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/transcoded_suffix_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/transcoded_suffix_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_transcoded_suffix" />
<TextView
android:id="@+id/transcoded_suffix_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/duration_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/duration_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_duration" />
<TextView
android:id="@+id/duration_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/bitrate_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/bitrate_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_bitrate" />
<TextView
android:id="@+id/bitrate_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/path_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/path_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_path" />
<TextView
android:id="@+id/path_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
<View
style="@style/Divider"
android:layout_gravity="center_vertical"
android:layout_marginVertical="8dp" />
<LinearLayout
android:id="@+id/disc_number_info_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/disc_number_key_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="4"
android:paddingEnd="8dp"
android:text="@string/track_info_disc_number" />
<TextView
android:id="@+id/disc_number_value_sector"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="7"
android:text="@string/label_placeholder" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@@ -753,6 +753,40 @@
android:id="@+id/home_recently_added_albums_placeholder"
layout="@layout/item_placeholder_album"
android:visibility="gone" />
<!-- Shares -->
<LinearLayout
android:id="@+id/shares_sector"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone">
<TextView
android:id="@+id/shares_text_view_refreshable"
style="@style/TitleLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="@string/home_title_shares" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/shares_recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:clipToPadding="false"
android:paddingTop="8dp"
android:paddingBottom="8dp" />
</LinearLayout>
<include
android:id="@+id/shares_placeholder"
layout="@layout/item_placeholder_horizontal"
android:visibility="gone" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

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