mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-30 14:22:05 +00:00
Compare commits
91 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cf62c8c0c | ||
|
|
330556ec33 | ||
|
|
4c8c5ce120 | ||
|
|
55ae9a8442 | ||
|
|
f8a53c7db2 | ||
|
|
b58cae1ecd | ||
|
|
e305f20811 | ||
|
|
c8c1bcfd3e | ||
|
|
e1c96d278f | ||
|
|
3783a2f790 | ||
|
|
63e288d147 | ||
|
|
d3ca43ed49 | ||
|
|
f0d31e425a | ||
|
|
609fc70d33 | ||
|
|
0b92c40d51 | ||
|
|
eb6a2b609e | ||
|
|
20ffc960df | ||
|
|
48d9022f9a | ||
|
|
9e6926fc97 | ||
|
|
780f1c3a2e | ||
|
|
0f471a7b9f | ||
|
|
4ec1519063 | ||
|
|
618cf23e6e | ||
|
|
e8e24354ec | ||
|
|
fd0fd0546c | ||
|
|
030ca82c3a | ||
|
|
0e6b860e03 | ||
|
|
8c288e8938 | ||
|
|
4e968f3397 | ||
|
|
b1e0f49ddb | ||
|
|
73a1ab2330 | ||
|
|
8540348670 | ||
|
|
67e4079732 | ||
|
|
6c6d56f451 | ||
|
|
f967df8ef3 | ||
|
|
c5a78bf945 | ||
|
|
6f51fd92bb | ||
|
|
3c2ea38f1e | ||
|
|
edf140fb5d | ||
|
|
049ce7713a | ||
|
|
8c49ceffdb | ||
|
|
052e9d9068 | ||
|
|
4d1213c43d | ||
|
|
1ec0c7b99c | ||
|
|
07f8914a9f | ||
|
|
965a80462c | ||
|
|
349c961f1a | ||
|
|
eb9f824c01 | ||
|
|
e465892013 | ||
|
|
a49c78b9f1 | ||
|
|
436ef21a29 | ||
|
|
be8decfac3 | ||
|
|
7c87ec2cbe | ||
|
|
fb7296b467 | ||
|
|
078aa87521 | ||
|
|
54a4355793 | ||
|
|
e84f62220c | ||
|
|
176db09662 | ||
|
|
2c3aebc83b | ||
|
|
a67571ee4f | ||
|
|
79f5b9b158 | ||
|
|
f6b176a357 | ||
|
|
aa5290c7ee | ||
|
|
0a26f0a7b8 | ||
|
|
c243fa9edc | ||
|
|
f94e5892cd | ||
|
|
477331da6f | ||
|
|
c4e8fe5261 | ||
|
|
92fd6b01e4 | ||
|
|
263d9ebc5f | ||
|
|
f6578afb14 | ||
|
|
41b374ab23 | ||
|
|
4448a632af | ||
|
|
b3b1c5b006 | ||
|
|
25900c848a | ||
|
|
08e9be107b | ||
|
|
6cbb2ee117 | ||
|
|
dacaa03eb7 | ||
|
|
a3d8b75d07 | ||
|
|
d08c113d99 | ||
|
|
2db716a79c | ||
|
|
71b913be9b | ||
|
|
fb353a33d9 | ||
|
|
240498c219 | ||
|
|
1eac053d2d | ||
|
|
160222563c | ||
|
|
4ec3d6bde7 | ||
|
|
e0f276dd2a | ||
|
|
acee7f8fa9 | ||
|
|
3d3d0fa856 | ||
|
|
0c2b18326e |
2
.github/workflows/github_release.yml
vendored
2
.github/workflows/github_release.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
BUILD_TOOLS_VERSION: ${{ env.BUILD_TOOL_VERSION }}
|
||||
|
||||
- name: Make artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: app-release-signed
|
||||
path: ${{steps.sign_apk.outputs.signedReleaseFile}}
|
||||
|
||||
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -4,6 +4,7 @@
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
|
||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@@ -1,3 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DesignSurface">
|
||||
<option name="filePathToZoomLevelMap">
|
||||
@@ -191,7 +192,7 @@
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/CappielloAntonio/tempo/releases"><img src="https://i.ibb.co/q0mdc4Z/get-it-on-github.png" width="200"></a>
|
||||
</p>
|
||||
<p align="center">
|
||||
<a href="https://f-droid.org/packages/com.cappielloantonio.notquitemy.tempo"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" width="200"></a>
|
||||
<a href="https://apt.izzysoft.de/fdroid/index/apk/com.cappielloantonio.tempo"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" width="200"></a>
|
||||
</p>
|
||||
@@ -18,6 +20,8 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
|
||||
|
||||
**If you find Tempo useful, please consider starring the project on GitHub. It would mean a lot to me and help promote the app to a wider audience.**
|
||||
|
||||
**Use the Github version of the app for full Android Auto and Chromecast support.**
|
||||
|
||||
## Features
|
||||
- **Subsonic Integration**: Tempo seamlessly integrates with your Subsonic server, providing you with easy access to your entire music collection on the go.
|
||||
- **Sleek and Intuitive UI**: Enjoy a clean and user-friendly interface designed to enhance your music listening experience, tailored to your preferences and listening history.
|
||||
@@ -29,6 +33,7 @@ Tempo does not rely on magic algorithms to decide what you should listen to. Ins
|
||||
- **Scrobbling Integration**: Optionally integrate Tempo with Last.fm to scrobble your played tracks, gather music insights, and further personalize your music recommendations, if supported by your Subsonic server.
|
||||
- **Podcasts and Radio**: If your Subsonic server supports it, listen to podcasts and radio shows directly within Tempo, expanding your audio entertainment options.
|
||||
- **Transcoding Support**: Activate transcoding of tracks on your Subsonic server, allowing you to set a transcoding profile for optimized streaming directly from the app. This feature requires support from your Subsonic server.
|
||||
- **Android Auto Support**: Enjoy your favorite music on the go with full Android Auto integration, allowing you to seamlessly control and listen to your tracks directly from your mobile device while driving.
|
||||
|
||||
<p align="center">
|
||||
<img src="mockup/feat/1_screenshot.png" width=200>
|
||||
|
||||
@@ -3,15 +3,15 @@ apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
|
||||
android {
|
||||
compileSdk = 34
|
||||
buildToolsVersion = '34.0.0'
|
||||
compileSdk 35
|
||||
buildToolsVersion = '35.0.0'
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 34
|
||||
targetSdk 35
|
||||
|
||||
versionCode 24
|
||||
versionName '3.7.0'
|
||||
versionCode 26
|
||||
versionName '3.9.0'
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -37,6 +37,11 @@ android {
|
||||
dimension = "default"
|
||||
applicationId "com.cappielloantonio.notquitemy.tempo"
|
||||
}
|
||||
|
||||
play {
|
||||
dimension = "default"
|
||||
applicationId "com.cappielloantonio.play.tempo"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@@ -59,22 +64,25 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
namespace 'com.cappielloantonio.tempo'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation files('../libs/lib-decoder-ffmpeg-release.aar')
|
||||
|
||||
// AndroidX
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.7.7'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.7.7'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.6'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.8.6'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.4.0'
|
||||
implementation 'androidx.room:room-runtime:2.6.1'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
implementation "androidx.appcompat:appcompat:1.6.1"
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
|
||||
// Android Material
|
||||
implementation 'com.google.android.material:material:1.10.0'
|
||||
@@ -84,17 +92,19 @@ dependencies {
|
||||
implementation 'com.github.bumptech.glide:annotations:4.16.0'
|
||||
|
||||
// Media3
|
||||
implementation 'androidx.media3:media3-session:1.3.0'
|
||||
implementation 'androidx.media3:media3-common:1.3.0'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.3.0'
|
||||
implementation 'androidx.media3:media3-ui:1.3.0'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.3.0'
|
||||
implementation 'androidx.media3:media3-session:1.5.1'
|
||||
implementation 'androidx.media3:media3-common:1.5.1'
|
||||
implementation 'androidx.media3:media3-exoplayer:1.5.1'
|
||||
implementation 'androidx.media3:media3-ui:1.5.1'
|
||||
implementation 'androidx.media3:media3-exoplayer-hls:1.5.1'
|
||||
tempoImplementation 'androidx.media3:media3-cast:1.5.1'
|
||||
playImplementation 'androidx.media3:media3-cast:1.5.1'
|
||||
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.6.1'
|
||||
|
||||
// Retrofit
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.10.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.12'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.10.0'
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.11.0'
|
||||
implementation 'com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.14'
|
||||
implementation 'com.squareup.retrofit2:converter-gson:2.11.0'
|
||||
}
|
||||
1065
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/10.json
Normal file
1065
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/10.json
Normal file
File diff suppressed because it is too large
Load Diff
1027
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json
Normal file
1027
app/schemas/com.cappielloantonio.tempo.database.AppDatabase/9.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -4,8 +4,10 @@ import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.cappielloantonio.tempo.github.Github;
|
||||
import com.cappielloantonio.tempo.helper.ThemeHelper;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
|
||||
@@ -15,6 +17,7 @@ public class App extends Application {
|
||||
private static App instance;
|
||||
private static Context context;
|
||||
private static Subsonic subsonic;
|
||||
private static Github github;
|
||||
private static SharedPreferences preferences;
|
||||
|
||||
@Override
|
||||
@@ -53,6 +56,13 @@ public class App extends Application {
|
||||
return subsonic;
|
||||
}
|
||||
|
||||
public static Github getGithubClientInstance() {
|
||||
if (github == null) {
|
||||
github = new Github();
|
||||
}
|
||||
return github;
|
||||
}
|
||||
|
||||
public SharedPreferences getPreferences() {
|
||||
if (preferences == null) {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
@@ -61,18 +71,12 @@ public class App extends Application {
|
||||
return preferences;
|
||||
}
|
||||
|
||||
private static Subsonic getSubsonicClient() {
|
||||
String server = Preferences.getServer();
|
||||
String username = Preferences.getUser();
|
||||
String password = Preferences.getPassword();
|
||||
String token = Preferences.getToken();
|
||||
String salt = Preferences.getSalt();
|
||||
boolean isLowSecurity = Preferences.isLowScurity();
|
||||
public static void refreshSubsonicClient() {
|
||||
subsonic = getSubsonicClient();
|
||||
}
|
||||
|
||||
SubsonicPreferences preferences = new SubsonicPreferences();
|
||||
preferences.setServerUrl(server);
|
||||
preferences.setUsername(username);
|
||||
preferences.setAuthentication(password, token, salt, isLowSecurity);
|
||||
private static Subsonic getSubsonicClient() {
|
||||
SubsonicPreferences preferences = getSubsonicPreferences();
|
||||
|
||||
if (preferences.getAuthentication() != null) {
|
||||
if (preferences.getAuthentication().getPassword() != null)
|
||||
@@ -85,4 +89,21 @@ public class App extends Application {
|
||||
|
||||
return new Subsonic(preferences);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static SubsonicPreferences getSubsonicPreferences() {
|
||||
String server = Preferences.getInUseServerAddress();
|
||||
String username = Preferences.getUser();
|
||||
String password = Preferences.getPassword();
|
||||
String token = Preferences.getToken();
|
||||
String salt = Preferences.getSalt();
|
||||
boolean isLowSecurity = Preferences.isLowScurity();
|
||||
|
||||
SubsonicPreferences preferences = new SubsonicPreferences();
|
||||
preferences.setServerUrl(server);
|
||||
preferences.setUsername(username);
|
||||
preferences.setAuthentication(password, token, salt, isLowSecurity);
|
||||
|
||||
return preferences;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.cappielloantonio.tempo.database;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.room.AutoMigration;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
@@ -11,6 +12,7 @@ import com.cappielloantonio.tempo.database.converter.DateConverters;
|
||||
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.database.dao.PlaylistDao;
|
||||
import com.cappielloantonio.tempo.database.dao.QueueDao;
|
||||
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
|
||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||
@@ -22,11 +24,13 @@ import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
|
||||
@UnstableApi
|
||||
@Database(
|
||||
version = 8,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class},
|
||||
autoMigrations = {@AutoMigration(from = 7, to = 8)}
|
||||
version = 10,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class, Playlist.class},
|
||||
autoMigrations = {@AutoMigration(from = 9, to = 10)}
|
||||
)
|
||||
@TypeConverters({DateConverters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
@@ -56,4 +60,6 @@ public abstract class AppDatabase extends RoomDatabase {
|
||||
public abstract FavoriteDao favoriteDao();
|
||||
|
||||
public abstract SessionMediaItemDao sessionMediaItemDao();
|
||||
|
||||
public abstract PlaylistDao playlistDao();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public interface ChronologyDao {
|
||||
@Query("SELECT * FROM chronology WHERE server == :server GROUP BY id ORDER BY timestamp DESC LIMIT :count")
|
||||
LiveData<List<Chronology>> getLastPlayed(String server, int count);
|
||||
|
||||
@Query("SELECT * FROM chronology WHERE timestamp >= :startDate AND timestamp < :endDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 9")
|
||||
@Query("SELECT * FROM chronology WHERE timestamp >= :endDate AND timestamp < :startDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 20")
|
||||
LiveData<List<Chronology>> getAllFrom(long startDate, long endDate, String server);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadDao {
|
||||
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, track ASC")
|
||||
@Query("SELECT * FROM download WHERE download_state = 1 ORDER BY artist, album, disc_number, track ASC")
|
||||
LiveData<List<Download>> getAll();
|
||||
|
||||
@Query("SELECT * FROM download WHERE id = :id")
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.cappielloantonio.tempo.github;
|
||||
|
||||
import com.cappielloantonio.tempo.github.api.release.ReleaseClient;
|
||||
|
||||
public class Github {
|
||||
private static final String OWNER = "CappielloAntonio";
|
||||
private static final String REPO = "Tempo";
|
||||
private ReleaseClient releaseClient;
|
||||
|
||||
public ReleaseClient getReleaseClient() {
|
||||
if (releaseClient == null) {
|
||||
releaseClient = new ReleaseClient(this);
|
||||
}
|
||||
|
||||
return releaseClient;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
return "https://api.github.com/";
|
||||
}
|
||||
|
||||
public static String getOwner() {
|
||||
return OWNER;
|
||||
}
|
||||
|
||||
public static String getRepo() {
|
||||
return REPO;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.cappielloantonio.tempo.github
|
||||
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
|
||||
class GithubRetrofitClient(github: Github) {
|
||||
var retrofit: Retrofit
|
||||
|
||||
init {
|
||||
retrofit = Retrofit.Builder()
|
||||
.baseUrl(github.url)
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(getOkHttpClient())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getOkHttpClient(): OkHttpClient {
|
||||
return OkHttpClient.Builder()
|
||||
.addInterceptor(getHttpLoggingInterceptor())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val loggingInterceptor = HttpLoggingInterceptor()
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
return loggingInterceptor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.cappielloantonio.tempo.github.api.release;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.github.Github;
|
||||
import com.cappielloantonio.tempo.github.GithubRetrofitClient;
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class ReleaseClient {
|
||||
private static final String TAG = "ReleaseClient";
|
||||
|
||||
private final ReleaseService releaseService;
|
||||
|
||||
public ReleaseClient(Github github) {
|
||||
this.releaseService = new GithubRetrofitClient(github).getRetrofit().create(ReleaseService.class);
|
||||
}
|
||||
|
||||
public Call<LatestRelease> getLatestRelease() {
|
||||
Log.d(TAG, "getLatestRelease()");
|
||||
return releaseService.getLatestRelease(Github.getOwner(), Github.getRepo());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cappielloantonio.tempo.github.api.release;
|
||||
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Path;
|
||||
|
||||
public interface ReleaseService {
|
||||
@GET("repos/{owner}/{repo}/releases/latest")
|
||||
Call<LatestRelease> getLatestRelease(@Path("owner") String owner, @Path("repo") String repo);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.cappielloantonio.tempo.github.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class Assets(
|
||||
@SerializedName("url")
|
||||
var url: String? = null,
|
||||
@SerializedName("id")
|
||||
var id: Int? = null,
|
||||
@SerializedName("node_id")
|
||||
var nodeId: String? = null,
|
||||
@SerializedName("name")
|
||||
var name: String? = null,
|
||||
@SerializedName("label")
|
||||
var label: String? = null,
|
||||
@SerializedName("uploader")
|
||||
var uploader: Uploader? = Uploader(),
|
||||
@SerializedName("content_type")
|
||||
var contentType: String? = null,
|
||||
@SerializedName("state")
|
||||
var state: String? = null,
|
||||
@SerializedName("size")
|
||||
var size: Int? = null,
|
||||
@SerializedName("download_count")
|
||||
var downloadCount: Int? = null,
|
||||
@SerializedName("created_at")
|
||||
var createdAt: String? = null,
|
||||
@SerializedName("updated_at")
|
||||
var updatedAt: String? = null,
|
||||
@SerializedName("browser_download_url")
|
||||
var browserDownloadUrl: String? = null
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.cappielloantonio.tempo.github.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class Author(
|
||||
@SerializedName("login")
|
||||
var login: String? = null,
|
||||
@SerializedName("id")
|
||||
var id: Int? = null,
|
||||
@SerializedName("node_id")
|
||||
var nodeId: String? = null,
|
||||
@SerializedName("avatar_url")
|
||||
var avatarUrl: String? = null,
|
||||
@SerializedName("gravatar_id")
|
||||
var gravatarId: String? = null,
|
||||
@SerializedName("url")
|
||||
var url: String? = null,
|
||||
@SerializedName("html_url")
|
||||
var htmlUrl: String? = null,
|
||||
@SerializedName("followers_url")
|
||||
var followersUrl: String? = null,
|
||||
@SerializedName("following_url")
|
||||
var followingUrl: String? = null,
|
||||
@SerializedName("gists_url")
|
||||
var gistsUrl: String? = null,
|
||||
@SerializedName("starred_url")
|
||||
var starredUrl: String? = null,
|
||||
@SerializedName("subscriptions_url")
|
||||
var subscriptionsUrl: String? = null,
|
||||
@SerializedName("organizations_url")
|
||||
var organizationsUrl: String? = null,
|
||||
@SerializedName("repos_url")
|
||||
var reposUrl: String? = null,
|
||||
@SerializedName("events_url")
|
||||
var eventsUrl: String? = null,
|
||||
@SerializedName("received_events_url")
|
||||
var receivedEventsUrl: String? = null,
|
||||
@SerializedName("type")
|
||||
var type: String? = null,
|
||||
@SerializedName("site_admin")
|
||||
var siteAdmin: Boolean? = null
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.cappielloantonio.tempo.github.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class LatestRelease(
|
||||
@SerializedName("url")
|
||||
var url: String? = null,
|
||||
@SerializedName("assets_url")
|
||||
var assetsUrl: String? = null,
|
||||
@SerializedName("upload_url")
|
||||
var uploadUrl: String? = null,
|
||||
@SerializedName("html_url")
|
||||
var htmlUrl: String? = null,
|
||||
@SerializedName("id")
|
||||
var id: Int? = null,
|
||||
@SerializedName("author")
|
||||
var author: Author? = Author(),
|
||||
@SerializedName("node_id")
|
||||
var nodeId: String? = null,
|
||||
@SerializedName("tag_name")
|
||||
var tagName: String? = null,
|
||||
@SerializedName("target_commitish")
|
||||
var targetCommitish: String? = null,
|
||||
@SerializedName("name")
|
||||
var name: String? = null,
|
||||
@SerializedName("draft")
|
||||
var draft: Boolean? = null,
|
||||
@SerializedName("prerelease")
|
||||
var prerelease: Boolean? = null,
|
||||
@SerializedName("created_at")
|
||||
var createdAt: String? = null,
|
||||
@SerializedName("published_at")
|
||||
var publishedAt: String? = null,
|
||||
@SerializedName("assets")
|
||||
var assets: ArrayList<Assets> = arrayListOf(),
|
||||
@SerializedName("tarball_url")
|
||||
var tarballUrl: String? = null,
|
||||
@SerializedName("zipball_url")
|
||||
var zipballUrl: String? = null,
|
||||
@SerializedName("body")
|
||||
var body: String? = null,
|
||||
@SerializedName("reactions")
|
||||
var reactions: Reactions? = Reactions()
|
||||
)
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.cappielloantonio.tempo.github.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class Reactions(
|
||||
@SerializedName("url")
|
||||
var url: String? = null,
|
||||
@SerializedName("total_count")
|
||||
var totalCount: Int? = null,
|
||||
@SerializedName("+1")
|
||||
var like: Int? = null,
|
||||
@SerializedName("-1")
|
||||
var dislike: Int? = null,
|
||||
@SerializedName("laugh")
|
||||
var laugh: Int? = null,
|
||||
@SerializedName("hooray")
|
||||
var hooray: Int? = null,
|
||||
@SerializedName("confused")
|
||||
var confused: Int? = null,
|
||||
@SerializedName("heart")
|
||||
var heart: Int? = null,
|
||||
@SerializedName("rocket")
|
||||
var rocket: Int? = null,
|
||||
@SerializedName("eyes")
|
||||
var eyes: Int? = null
|
||||
)
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.cappielloantonio.tempo.github.models
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
@Keep
|
||||
data class Uploader(
|
||||
@SerializedName("login")
|
||||
var login: String? = null,
|
||||
@SerializedName("id")
|
||||
var id: Int? = null,
|
||||
@SerializedName("node_id")
|
||||
var nodeId: String? = null,
|
||||
@SerializedName("avatar_url")
|
||||
var avatarUrl: String? = null,
|
||||
@SerializedName("gravatar_id")
|
||||
var gravatarId: String? = null,
|
||||
@SerializedName("url")
|
||||
var url: String? = null,
|
||||
@SerializedName("html_url")
|
||||
var htmlUrl: String? = null,
|
||||
@SerializedName("followers_url")
|
||||
var followersUrl: String? = null,
|
||||
@SerializedName("following_url")
|
||||
var followingUrl: String? = null,
|
||||
@SerializedName("gists_url")
|
||||
var gistsUrl: String? = null,
|
||||
@SerializedName("starred_url")
|
||||
var starredUrl: String? = null,
|
||||
@SerializedName("subscriptions_url")
|
||||
var subscriptionsUrl: String? = null,
|
||||
@SerializedName("organizations_url")
|
||||
var organizationsUrl: String? = null,
|
||||
@SerializedName("repos_url")
|
||||
var reposUrl: String? = null,
|
||||
@SerializedName("events_url")
|
||||
var eventsUrl: String? = null,
|
||||
@SerializedName("received_events_url")
|
||||
var receivedEventsUrl: String? = null,
|
||||
@SerializedName("type")
|
||||
var type: String? = null,
|
||||
@SerializedName("site_admin")
|
||||
var siteAdmin: Boolean? = null
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.cappielloantonio.tempo.github.utils;
|
||||
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
|
||||
public class UpdateUtil {
|
||||
|
||||
public static boolean showUpdateDialog(LatestRelease release) {
|
||||
if (release.getTagName() == null) return false;
|
||||
|
||||
try {
|
||||
String[] local = BuildConfig.VERSION_NAME.split("\\.");
|
||||
String[] remote = release.getTagName().split("\\.");
|
||||
|
||||
for (int i = 0; i < local.length; i++) {
|
||||
int localPart = Integer.parseInt(local[i]);
|
||||
int remotePart = Integer.parseInt(remote[i]);
|
||||
|
||||
if (localPart > remotePart) {
|
||||
return false;
|
||||
} else if (localPart < remotePart) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
@@ -27,6 +28,9 @@ data class Server(
|
||||
@ColumnInfo(name = "address")
|
||||
val address: String,
|
||||
|
||||
@ColumnInfo(name = "local_address")
|
||||
val localAddress: String?,
|
||||
|
||||
@ColumnInfo(name = "timestamp")
|
||||
val timestamp: Long,
|
||||
|
||||
|
||||
@@ -236,12 +236,12 @@ class SessionMediaItem() {
|
||||
.setMediaId(id!!)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(title))
|
||||
.setTitle(title)
|
||||
.setTrackNumber(track ?: 0)
|
||||
.setDiscNumber(discNumber ?: 0)
|
||||
.setReleaseYear(year ?: 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(album))
|
||||
.setArtist(MusicUtil.getReadableString(artist))
|
||||
.setAlbumTitle(album)
|
||||
.setArtist(artist)
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
|
||||
@@ -12,28 +12,8 @@ import java.util.List;
|
||||
public class ChronologyRepository {
|
||||
private final ChronologyDao chronologyDao = AppDatabase.getInstance().chronologyDao();
|
||||
|
||||
public LiveData<List<Chronology>> getThisWeek(String server) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
|
||||
Calendar first = (Calendar) calendar.clone();
|
||||
first.add(Calendar.DAY_OF_WEEK, first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK));
|
||||
|
||||
Calendar last = (Calendar) first.clone();
|
||||
last.add(Calendar.DAY_OF_YEAR, 6);
|
||||
|
||||
return chronologyDao.getAllFrom(first.getTime().getTime(), last.getTime().getTime(), server);
|
||||
}
|
||||
|
||||
public LiveData<List<Chronology>> getLastWeek(String server) {
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
|
||||
Calendar first = (Calendar) calendar.clone();
|
||||
first.add(Calendar.DAY_OF_WEEK, first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK) - 6);
|
||||
|
||||
Calendar last = (Calendar) first.clone();
|
||||
last.add(Calendar.DAY_OF_YEAR, 6);
|
||||
|
||||
return chronologyDao.getAllFrom(first.getTime().getTime(), last.getTime().getTime(), server);
|
||||
public LiveData<List<Chronology>> getChronology(String server, long start, long end) {
|
||||
return chronologyDao.getAllFrom(start, end, server);
|
||||
}
|
||||
|
||||
public void insert(Chronology item) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.PlaylistDao;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
@@ -17,6 +20,7 @@ import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class PlaylistRepository {
|
||||
private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao();
|
||||
public MutableLiveData<List<Playlist>> getPlaylists(boolean random, int size) {
|
||||
MutableLiveData<List<Playlist>> listLivePlaylists = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
@@ -153,4 +157,50 @@ public class PlaylistRepository {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<List<Playlist>> getPinnedPlaylists() {
|
||||
return playlistDao.getAll();
|
||||
}
|
||||
|
||||
public void insert(Playlist playlist) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void delete(Playlist playlist) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(playlistDao, playlist);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final PlaylistDao playlistDao;
|
||||
private final Playlist playlist;
|
||||
|
||||
public InsertThreadSafe(PlaylistDao playlistDao, Playlist playlist) {
|
||||
this.playlistDao = playlistDao;
|
||||
this.playlist = playlist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
playlistDao.insert(playlist);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final PlaylistDao playlistDao;
|
||||
private final Playlist playlist;
|
||||
|
||||
public DeleteThreadSafe(PlaylistDao playlistDao, Playlist playlist) {
|
||||
this.playlistDao = playlistDao;
|
||||
this.playlist = playlist;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
playlistDao.delete(playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
|
||||
@@ -54,12 +50,12 @@ public class SongRepository {
|
||||
return starredSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(Child song, int count) {
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(song.getId(), count)
|
||||
.getSimilarSongs2(id, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
import com.cappielloantonio.tempo.interfaces.SystemCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
|
||||
@@ -58,6 +61,8 @@ public class SystemRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
pingResult.postValue(response.body().getSubsonicResponse());
|
||||
} else {
|
||||
pingResult.postValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,4 +97,27 @@ public class SystemRepository {
|
||||
|
||||
return extensionsResult;
|
||||
}
|
||||
|
||||
public MutableLiveData<LatestRelease> checkTempoUpdate() {
|
||||
MutableLiveData<LatestRelease> latestRelease = new MutableLiveData<>();
|
||||
|
||||
App.getGithubClientInstance()
|
||||
.getReleaseClient()
|
||||
.getLatestRelease()
|
||||
.enqueue(new Callback<LatestRelease>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<LatestRelease> call, @NonNull Response<LatestRelease> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
latestRelease.postValue(response.body());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<LatestRelease> call, @NonNull Throwable t) {
|
||||
latestRelease.postValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return latestRelease;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import android.content.ComponentName;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
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.App;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaIndexCallback;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.repository.ChronologyRepository;
|
||||
@@ -299,6 +307,30 @@ public class MediaManager {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public static void continuousPlay(MediaItem mediaItem) {
|
||||
if (mediaItem != null && Preferences.isContinuousPlayEnabled() && Preferences.isInstantMixUsable()) {
|
||||
Preferences.setLastInstantMix();
|
||||
|
||||
LiveData<List<Child>> instantMix = getSongRepository().getInstantMix(mediaItem.mediaId, 10);
|
||||
instantMix.observeForever(new Observer<List<Child>>() {
|
||||
@Override
|
||||
public void onChanged(List<Child> media) {
|
||||
if (media != null) {
|
||||
ListenableFuture<MediaBrowser> mediaBrowserListenableFuture = new MediaBrowser.Builder(
|
||||
App.getContext(),
|
||||
new SessionToken(App.getContext(), new ComponentName(App.getContext(), MediaService.class))
|
||||
).buildAsync();
|
||||
|
||||
enqueue(mediaBrowserListenableFuture, media, true);
|
||||
}
|
||||
|
||||
instantMix.removeObserver(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveChronology(MediaItem mediaItem) {
|
||||
if (mediaItem != null) {
|
||||
getChronologyRepository().insert(new Chronology(mediaItem));
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.util.*
|
||||
|
||||
@Keep
|
||||
@@ -17,9 +19,23 @@ open class AlbumID3 : Parcelable {
|
||||
var coverArtId: String? = null
|
||||
var songCount: Int? = 0
|
||||
var duration: Int? = 0
|
||||
var playCount: Long? = null
|
||||
var playCount: Long? = 0
|
||||
var created: Date? = null
|
||||
var starred: Date? = null
|
||||
var year: Int = 0
|
||||
var genre: String? = null
|
||||
var played: Date? = Date(0)
|
||||
var userRating: Int? = 0
|
||||
var recordLabels: List<RecordLabel>? = null
|
||||
var musicBrainzId: String? = null
|
||||
var genres: List<ItemGenre>? = null
|
||||
var artists: List<ArtistID3>? = null
|
||||
var displayArtist: String? = null
|
||||
var releaseTypes: List<String>? = null
|
||||
var moods: List<String>? = null
|
||||
var sortName: String? = null
|
||||
var originalReleaseDate: ItemDate? = null
|
||||
var releaseDate: ItemDate? = null
|
||||
var isCompilation: Boolean? = null
|
||||
var discTitles: List<DiscTitle>? = null
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
open class DiscTitle : Parcelable {
|
||||
var disc: Int? = null
|
||||
var title: String? = null
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
open class ItemDate : Parcelable {
|
||||
var year: Int? = null
|
||||
var month: Int? = null
|
||||
var day: Int? = null
|
||||
|
||||
fun getFormattedDate(): String {
|
||||
val calendar = Calendar.getInstance()
|
||||
val dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault())
|
||||
|
||||
calendar.set(year ?: 0, month ?: 0, day ?: 0)
|
||||
|
||||
return dateFormat.format(calendar.time)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
open class ItemGenre : Parcelable {
|
||||
var name: String? = null
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.cappielloantonio.tempo.subsonic.models
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
open class RecordLabel : Parcelable {
|
||||
var name: String? = null
|
||||
}
|
||||
@@ -2,7 +2,8 @@ package com.cappielloantonio.tempo.subsonic.utils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
|
||||
@@ -39,7 +40,19 @@ public class CacheUtil {
|
||||
|
||||
private boolean isConnected() {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return (netInfo != null && netInfo.isConnected());
|
||||
|
||||
if (connectivityManager != null) {
|
||||
Network network = connectivityManager.getActiveNetwork();
|
||||
|
||||
if (network != null) {
|
||||
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
|
||||
if (capabilities != null) {
|
||||
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -18,12 +19,16 @@ import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.BuildConfig;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.broadcast.receiver.ConnectivityStatusBroadcastReceiver;
|
||||
import com.cappielloantonio.tempo.databinding.ActivityMainBinding;
|
||||
import com.cappielloantonio.tempo.github.utils.UpdateUtil;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.ui.activity.base.BaseActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.ConnectionAlertDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.GithubTempoUpdateDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.ServerUnreachableDialog;
|
||||
import com.cappielloantonio.tempo.ui.fragment.PlayerBottomSheetFragment;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
@@ -39,7 +44,7 @@ import java.util.concurrent.ExecutionException;
|
||||
|
||||
@UnstableApi
|
||||
public class MainActivity extends BaseActivity {
|
||||
private static final String TAG = "MainActivity";
|
||||
private static final String TAG = "MainActivityLogs";
|
||||
|
||||
public ActivityMainBinding bind;
|
||||
private MainViewModel mainViewModel;
|
||||
@@ -71,6 +76,7 @@ public class MainActivity extends BaseActivity {
|
||||
init();
|
||||
checkConnectionType();
|
||||
getOpenSubsonicExtensions();
|
||||
checkTempoUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -301,10 +307,11 @@ public class MainActivity extends BaseActivity {
|
||||
Preferences.setToken(null);
|
||||
Preferences.setPassword(null);
|
||||
Preferences.setServer(null);
|
||||
Preferences.setLocalAddress(null);
|
||||
Preferences.setUser(null);
|
||||
|
||||
// TODO Enter all settings to be reset
|
||||
Preferences.setServerId(null);
|
||||
Preferences.setOpenSubsonic(false);
|
||||
Preferences.setPlaybackSpeed(Constants.MEDIA_PLAYBACK_SPEED_100);
|
||||
Preferences.setSkipSilenceMode(false);
|
||||
Preferences.setDataSavingMode(false);
|
||||
@@ -334,17 +341,37 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void pingServer() {
|
||||
if (Preferences.getToken() != null) {
|
||||
mainViewModel.ping().observe(this, subsonicResponse -> {
|
||||
if (subsonicResponse == null && Preferences.showServerUnreachableDialog()) {
|
||||
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
if (Preferences.getToken() == null) return;
|
||||
|
||||
if (subsonicResponse != null) {
|
||||
if (Preferences.isInUseServerAddressLocal()) {
|
||||
mainViewModel.ping().observe(this, subsonicResponse -> {
|
||||
if (subsonicResponse == null) {
|
||||
Preferences.setServerSwitchableTimer();
|
||||
Preferences.switchInUseServerAddress();
|
||||
App.refreshSubsonicClient();
|
||||
pingServer();
|
||||
} else {
|
||||
Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (Preferences.isServerSwitchable()) {
|
||||
Preferences.setServerSwitchableTimer();
|
||||
Preferences.switchInUseServerAddress();
|
||||
App.refreshSubsonicClient();
|
||||
pingServer();
|
||||
} else {
|
||||
mainViewModel.ping().observe(this, subsonicResponse -> {
|
||||
if (subsonicResponse == null) {
|
||||
if (Preferences.showServerUnreachableDialog()) {
|
||||
ServerUnreachableDialog dialog = new ServerUnreachableDialog();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
} else {
|
||||
Preferences.setOpenSubsonic(subsonicResponse.getOpenSubsonic() != null && subsonicResponse.getOpenSubsonic());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,6 +385,17 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkTempoUpdate() {
|
||||
if (BuildConfig.FLAVOR.equals("tempo") && Preferences.showTempoUpdateDialog()) {
|
||||
mainViewModel.checkTempoUpdate().observe(this, latestRelease -> {
|
||||
if (latestRelease != null && UpdateUtil.showUpdateDialog(latestRelease)) {
|
||||
GithubTempoUpdateDialog dialog = new GithubTempoUpdateDialog(latestRelease);
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void checkConnectionType() {
|
||||
if (Preferences.isWifiOnly()) {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
@@ -38,8 +38,8 @@ public class AlbumAdapter extends RecyclerView.Adapter<AlbumAdapter.ViewHolder>
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
AlbumID3 album = albums.get(position);
|
||||
|
||||
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
holder.item.albumNameLabel.setText(album.getName());
|
||||
holder.item.artistNameLabel.setText(album.getArtist());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
|
||||
@@ -38,8 +38,8 @@ public class AlbumArtistPageOrSimilarAdapter extends RecyclerView.Adapter<AlbumA
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
AlbumID3 album = albums.get(position);
|
||||
|
||||
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
holder.item.albumNameLabel.setText(album.getName());
|
||||
holder.item.artistNameLabel.setText(album.getArtist());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
|
||||
@@ -80,8 +80,8 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
AlbumID3 album = albums.get(position);
|
||||
|
||||
holder.item.albumNameLabel.setText(MusicUtil.getReadableString(album.getName()));
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
holder.item.albumNameLabel.setText(album.getName());
|
||||
holder.item.artistNameLabel.setText(album.getArtist());
|
||||
holder.item.artistNameLabel.setVisibility(showArtist ? View.VISIBLE : View.GONE);
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
@@ -169,6 +169,14 @@ public class AlbumCatalogueAdapter extends RecyclerView.Adapter<AlbumCatalogueAd
|
||||
albums.sort(Comparator.comparing(AlbumID3::getCreated));
|
||||
Collections.reverse(albums);
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_RECENTLY_PLAYED:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getPlayed));
|
||||
Collections.reverse(albums);
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_MOST_PLAYED:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getPlayCount));
|
||||
Collections.reverse(albums);
|
||||
break;
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.cappielloantonio.tempo.ui.adapter;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -11,22 +13,60 @@ import com.cappielloantonio.tempo.databinding.ItemHorizontalAlbumBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> {
|
||||
public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontalAdapter.ViewHolder> implements Filterable {
|
||||
private final ClickCallback click;
|
||||
private final boolean isOffline;
|
||||
|
||||
private List<AlbumID3> albumsFull;
|
||||
private List<AlbumID3> albums;
|
||||
private String currentFilter;
|
||||
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
List<AlbumID3> filteredList = new ArrayList<>();
|
||||
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
filteredList.addAll(albumsFull);
|
||||
} else {
|
||||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||||
currentFilter = filterPattern;
|
||||
|
||||
for (AlbumID3 item : albumsFull) {
|
||||
if (item.getName().toLowerCase().contains(filterPattern)) {
|
||||
filteredList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResults results = new FilterResults();
|
||||
results.values = filteredList;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
albums = (List<AlbumID3>) results.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
public AlbumHorizontalAdapter(ClickCallback click, boolean isOffline) {
|
||||
this.click = click;
|
||||
this.isOffline = isOffline;
|
||||
this.albums = Collections.emptyList();
|
||||
this.albumsFull = Collections.emptyList();
|
||||
this.currentFilter = "";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -40,8 +80,8 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
AlbumID3 album = albums.get(position);
|
||||
|
||||
holder.item.albumTitleTextView.setText(MusicUtil.getReadableString(album.getName()));
|
||||
holder.item.albumArtistTextView.setText(MusicUtil.getReadableString(album.getArtist()));
|
||||
holder.item.albumTitleTextView.setText(album.getName());
|
||||
holder.item.albumArtistTextView.setText(album.getArtist());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
@@ -55,10 +95,16 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
|
||||
}
|
||||
|
||||
public void setItems(List<AlbumID3> albums) {
|
||||
this.albums = albums;
|
||||
this.albumsFull = albums != null ? albums : Collections.emptyList();
|
||||
filtering.filter(currentFilter);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return filtering;
|
||||
}
|
||||
|
||||
public AlbumID3 getItem(int id) {
|
||||
return albums.get(id);
|
||||
}
|
||||
@@ -95,4 +141,21 @@ public class AlbumHorizontalAdapter extends RecyclerView.Adapter<AlbumHorizontal
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sort(String order) {
|
||||
switch (order) {
|
||||
case Constants.ALBUM_ORDER_BY_NAME:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getName));
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_MOST_RECENTLY_STARRED:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
break;
|
||||
case Constants.ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED:
|
||||
albums.sort(Comparator.comparing(AlbumID3::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ArtistAdapter extends RecyclerView.Adapter<ArtistAdapter.ViewHolder
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
ArtistID3 artist = artists.get(position);
|
||||
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
holder.item.artistNameLabel.setText(artist.getName());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
|
||||
@@ -50,7 +50,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
artists.clear();
|
||||
artists.addAll((List) results.values);
|
||||
if (results.count > 0) artists.addAll((List) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
@@ -74,7 +74,7 @@ public class ArtistCatalogueAdapter extends RecyclerView.Adapter<ArtistCatalogue
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
ArtistID3 artist = artists.get(position);
|
||||
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
holder.item.artistNameLabel.setText(artist.getName());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@@ -11,21 +13,59 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalArtistBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> {
|
||||
public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizontalAdapter.ViewHolder> implements Filterable {
|
||||
private final ClickCallback click;
|
||||
|
||||
private List<ArtistID3> artistsFull;
|
||||
private List<ArtistID3> artists;
|
||||
private String currentFilter;
|
||||
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
List<ArtistID3> filteredList = new ArrayList<>();
|
||||
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
filteredList.addAll(artistsFull);
|
||||
} else {
|
||||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||||
currentFilter = filterPattern;
|
||||
|
||||
for (ArtistID3 item : artistsFull) {
|
||||
if (item.getName().toLowerCase().contains(filterPattern)) {
|
||||
filteredList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResults results = new FilterResults();
|
||||
results.values = filteredList;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
artists = (List<ArtistID3>) results.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
public ArtistHorizontalAdapter(ClickCallback click) {
|
||||
this.click = click;
|
||||
this.artists = Collections.emptyList();
|
||||
this.artistsFull = Collections.emptyList();
|
||||
this.currentFilter = "";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -39,7 +79,7 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
ArtistID3 artist = artists.get(position);
|
||||
|
||||
holder.item.artistNameTextView.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
holder.item.artistNameTextView.setText(artist.getName());
|
||||
|
||||
if (artist.getAlbumCount() > 0) {
|
||||
holder.item.artistInfoTextView.setText("Album count: " + artist.getAlbumCount());
|
||||
@@ -59,10 +99,16 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
|
||||
}
|
||||
|
||||
public void setItems(List<ArtistID3> artists) {
|
||||
this.artists = artists;
|
||||
this.artistsFull = artists != null ? artists : Collections.emptyList();
|
||||
filtering.filter(currentFilter);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return filtering;
|
||||
}
|
||||
|
||||
public ArtistID3 getItem(int id) {
|
||||
return artists.get(id);
|
||||
}
|
||||
@@ -109,4 +155,21 @@ public class ArtistHorizontalAdapter extends RecyclerView.Adapter<ArtistHorizont
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sort(String order) {
|
||||
switch (order) {
|
||||
case Constants.ARTIST_ORDER_BY_NAME:
|
||||
artists.sort(Comparator.comparing(ArtistID3::getName));
|
||||
break;
|
||||
case Constants.ARTIST_ORDER_BY_MOST_RECENTLY_STARRED:
|
||||
artists.sort(Comparator.comparing(ArtistID3::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
break;
|
||||
case Constants.ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED:
|
||||
artists.sort(Comparator.comparing(ArtistID3::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ArtistSimilarAdapter extends RecyclerView.Adapter<ArtistSimilarAdap
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
SimilarArtistID3 artist = artists.get(position);
|
||||
|
||||
holder.item.artistNameLabel.setText(MusicUtil.getReadableString(artist.getName()));
|
||||
holder.item.artistNameLabel.setText(artist.getName());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), artist.getCoverArtId(), CustomGlideRequest.ResourceType.Artist)
|
||||
|
||||
@@ -39,8 +39,8 @@ public class DiscoverSongAdapter extends RecyclerView.Adapter<DiscoverSongAdapte
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.titleDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.albumDiscoverSongLabel.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
holder.item.titleDiscoverSongLabel.setText(song.getTitle());
|
||||
holder.item.albumDiscoverSongLabel.setText(song.getAlbum());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
|
||||
@@ -185,17 +185,17 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
private void initTrackLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.downloadedItemTitleTextView.setText(song.getTitle());
|
||||
holder.item.downloadedItemSubtitleTextView.setText(
|
||||
holder.itemView.getContext().getString(
|
||||
R.string.song_subtitle_formatter,
|
||||
MusicUtil.getReadableString(song.getArtist()),
|
||||
song.getArtist(),
|
||||
MusicUtil.getReadableDurationString(song.getDuration(), false),
|
||||
""
|
||||
)
|
||||
);
|
||||
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
holder.item.downloadedItemPreTextView.setText(song.getAlbum());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
@@ -216,9 +216,9 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
private void initAlbumLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getAlbum()));
|
||||
holder.item.downloadedItemTitleTextView.setText(song.getAlbum());
|
||||
holder.item.downloadedItemSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.download_item_single_subtitle_formatter, countSong(Constants.DOWNLOAD_TYPE_ALBUM, song.getAlbumId(), songs)));
|
||||
holder.item.downloadedItemPreTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
holder.item.downloadedItemPreTextView.setText(song.getArtist());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
@@ -239,7 +239,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
private void initArtistLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
holder.item.downloadedItemTitleTextView.setText(song.getArtist());
|
||||
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
|
||||
@@ -255,7 +255,7 @@ public class DownloadHorizontalAdapter extends RecyclerView.Adapter<DownloadHori
|
||||
private void initGenreLayout(ViewHolder holder, int position) {
|
||||
Child song = grouped.get(position);
|
||||
|
||||
holder.item.downloadedItemTitleTextView.setText(MusicUtil.getReadableString(song.getGenre()));
|
||||
holder.item.downloadedItemTitleTextView.setText(song.getGenre());
|
||||
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);
|
||||
|
||||
@@ -37,7 +37,7 @@ public class GenreAdapter extends RecyclerView.Adapter<GenreAdapter.ViewHolder>
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Genre genre = genres.get(position);
|
||||
|
||||
holder.item.genreLabel.setText(MusicUtil.getReadableString(genre.getGenre()));
|
||||
holder.item.genreLabel.setText(genre.getGenre());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.cappielloantonio.tempo.databinding.ItemLibraryCatalogueGenreBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Genre;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -49,7 +48,7 @@ public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAd
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
genres.clear();
|
||||
genres.addAll((List) results.values);
|
||||
if (results.count > 0) genres.addAll((List) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
@@ -73,7 +72,7 @@ public class GenreCatalogueAdapter extends RecyclerView.Adapter<GenreCatalogueAd
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Genre genre = genres.get(position);
|
||||
|
||||
holder.item.genreLabel.setText(MusicUtil.getReadableString(genre.getGenre()));
|
||||
holder.item.genreLabel.setText(genre.getGenre());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -48,11 +48,11 @@ public class PlayerSongQueueAdapter extends RecyclerView.Adapter<PlayerSongQueue
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.queueSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.queueSongTitleTextView.setText(song.getTitle());
|
||||
holder.item.queueSongSubtitleTextView.setText(
|
||||
holder.itemView.getContext().getString(
|
||||
R.string.song_subtitle_formatter,
|
||||
MusicUtil.getReadableString(song.getArtist()),
|
||||
song.getArtist(),
|
||||
MusicUtil.getReadableDurationString(song.getDuration(), false),
|
||||
MusicUtil.getReadableAudioQualityString(song)
|
||||
)
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PlaylistDialogHorizontalAdapter extends RecyclerView.Adapter<Playli
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Playlist playlist = playlists.get(position);
|
||||
|
||||
holder.item.playlistDialogTitleTextView.setText(MusicUtil.getReadableString(playlist.getName()));
|
||||
holder.item.playlistDialogTitleTextView.setText(playlist.getName());
|
||||
holder.item.playlistDialogCountTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
|
||||
}
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ public class PlaylistDialogSongHorizontalAdapter extends RecyclerView.Adapter<Pl
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.playlistDialogSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.playlistDialogAlbumArtistTextView.setText(MusicUtil.getReadableString(song.getArtist()));
|
||||
holder.item.playlistDialogSongTitleTextView.setText(song.getTitle());
|
||||
holder.item.playlistDialogAlbumArtistTextView.setText(song.getArtist());
|
||||
holder.item.playlistDialogSongDurationTextView.setText(MusicUtil.getReadableDurationString(song.getDuration(), false));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
|
||||
@@ -54,7 +54,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
playlists.clear();
|
||||
playlists.addAll((List) results.values);
|
||||
if (results.count > 0) playlists.addAll((List) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
@@ -75,7 +75,7 @@ public class PlaylistHorizontalAdapter extends RecyclerView.Adapter<PlaylistHori
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Playlist playlist = playlists.get(position);
|
||||
|
||||
holder.item.playlistTitleTextView.setText(MusicUtil.getReadableString(playlist.getName()));
|
||||
holder.item.playlistTitleTextView.setText(playlist.getName());
|
||||
holder.item.playlistSubtitleTextView.setText(holder.itemView.getContext().getString(R.string.playlist_counted_tracks, playlist.getSongCount(), MusicUtil.getReadableDurationString(playlist.getDuration(), false)));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
|
||||
@@ -48,7 +48,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
podcastChannels.clear();
|
||||
podcastChannels.addAll((List) results.values);
|
||||
if (results.count > 0) podcastChannels.addAll((List) results.values);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
@@ -72,7 +72,7 @@ public class PodcastChannelCatalogueAdapter extends RecyclerView.Adapter<Podcast
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
PodcastChannel podcastChannel = podcastChannels.get(position);
|
||||
|
||||
holder.item.podcastChannelTitleLabel.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
|
||||
holder.item.podcastChannelTitleLabel.setText(podcastChannel.getTitle());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), podcastChannel.getCoverArtId(), CustomGlideRequest.ResourceType.Podcast)
|
||||
|
||||
@@ -38,7 +38,7 @@ public class PodcastChannelHorizontalAdapter extends RecyclerView.Adapter<Podcas
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
PodcastChannel podcastChannel = podcastChannels.get(position);
|
||||
|
||||
holder.item.podcastChannelTitleTextView.setText(MusicUtil.getReadableString(podcastChannel.getTitle()));
|
||||
holder.item.podcastChannelTitleTextView.setText(podcastChannel.getTitle());
|
||||
holder.item.podcastChannelDescriptionTextView.setText(MusicUtil.getReadableString(podcastChannel.getDescription()));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
|
||||
@@ -45,8 +45,8 @@ public class PodcastEpisodeAdapter extends RecyclerView.Adapter<PodcastEpisodeAd
|
||||
PodcastEpisode podcastEpisode = podcastEpisodes.get(position);
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MMM d");
|
||||
|
||||
holder.item.podcastTitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getTitle()));
|
||||
holder.item.podcastSubtitleLabel.setText(MusicUtil.getReadableString(podcastEpisode.getArtist()));
|
||||
holder.item.podcastTitleLabel.setText(podcastEpisode.getTitle());
|
||||
holder.item.podcastSubtitleLabel.setText(podcastEpisode.getArtist());
|
||||
holder.item.podcastReleasesAndDurationLabel.setText(holder.itemView.getContext().getString(R.string.podcast_release_date_duration_formatter, simpleDateFormat.format(podcastEpisode.getPublishDate()), MusicUtil.getReadablePodcastDurationString(podcastEpisode.getDuration())));
|
||||
holder.item.podcastDescriptionText.setText(MusicUtil.getReadableString(podcastEpisode.getDescription()));
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ public class ShareHorizontalAdapter extends RecyclerView.Adapter<ShareHorizontal
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Share share = shares.get(position);
|
||||
|
||||
holder.item.shareTitleTextView.setText(MusicUtil.getReadableString(share.getDescription()));
|
||||
holder.item.shareTitleTextView.setText(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
|
||||
|
||||
@@ -38,7 +38,7 @@ public class SimilarTrackAdapter extends RecyclerView.Adapter<SimilarTrackAdapte
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.titleTrackLabel.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.titleTrackLabel.setText(song.getTitle());
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(holder.itemView.getContext(), song.getCoverArtId(), CustomGlideRequest.ResourceType.Song)
|
||||
|
||||
@@ -4,6 +4,8 @@ import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.Filterable;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
@@ -14,7 +16,9 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.ItemHorizontalTrackBinding;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.DiscTitle;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
@@ -22,21 +26,61 @@ import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
@UnstableApi
|
||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> {
|
||||
public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAdapter.ViewHolder> implements Filterable {
|
||||
private final ClickCallback click;
|
||||
private final boolean showCoverArt;
|
||||
private final boolean showAlbum;
|
||||
private final AlbumID3 album;
|
||||
|
||||
private List<Child> songsFull;
|
||||
private List<Child> songs;
|
||||
private String currentFilter;
|
||||
|
||||
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum) {
|
||||
private final Filter filtering = new Filter() {
|
||||
@Override
|
||||
protected FilterResults performFiltering(CharSequence constraint) {
|
||||
List<Child> filteredList = new ArrayList<>();
|
||||
|
||||
if (constraint == null || constraint.length() == 0) {
|
||||
filteredList.addAll(songsFull);
|
||||
} else {
|
||||
String filterPattern = constraint.toString().toLowerCase().trim();
|
||||
currentFilter = filterPattern;
|
||||
|
||||
for (Child item : songsFull) {
|
||||
if (item.getTitle().toLowerCase().contains(filterPattern)) {
|
||||
filteredList.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FilterResults results = new FilterResults();
|
||||
results.values = filteredList;
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(CharSequence constraint, FilterResults results) {
|
||||
songs = (List<Child>) results.values;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
};
|
||||
|
||||
public SongHorizontalAdapter(ClickCallback click, boolean showCoverArt, boolean showAlbum, AlbumID3 album) {
|
||||
this.click = click;
|
||||
this.showCoverArt = showCoverArt;
|
||||
this.showAlbum = showAlbum;
|
||||
this.songs = Collections.emptyList();
|
||||
this.songsFull = Collections.emptyList();
|
||||
this.currentFilter = "";
|
||||
this.album = album;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -50,16 +94,14 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
public void onBindViewHolder(ViewHolder holder, int position) {
|
||||
Child song = songs.get(position);
|
||||
|
||||
holder.item.searchResultSongTitleTextView.setText(MusicUtil.getReadableString(song.getTitle()));
|
||||
holder.item.searchResultSongTitleTextView.setText(song.getTitle());
|
||||
|
||||
holder.item.searchResultSongSubtitleTextView.setText(
|
||||
holder.itemView.getContext().getString(
|
||||
R.string.song_subtitle_formatter,
|
||||
MusicUtil.getReadableString(
|
||||
this.showAlbum ?
|
||||
song.getAlbum() :
|
||||
song.getArtist()
|
||||
),
|
||||
this.showAlbum ?
|
||||
song.getAlbum() :
|
||||
song.getArtist(),
|
||||
MusicUtil.getReadableDurationString(song.getDuration(), false),
|
||||
MusicUtil.getReadableAudioQualityString(song)
|
||||
)
|
||||
@@ -81,8 +123,28 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
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);
|
||||
if (!showCoverArt &&
|
||||
(position == 0 ||
|
||||
(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.differentDiskDividerSector.setVisibility(View.VISIBLE);
|
||||
|
||||
if (songs.get(position).getDiscNumber() != null && !Objects.requireNonNull(songs.get(position).getDiscNumber()).toString().isBlank()) {
|
||||
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titleless, songs.get(position).getDiscNumber().toString()));
|
||||
}
|
||||
|
||||
if (album.getDiscTitles() != null) {
|
||||
Optional<DiscTitle> discTitle = album.getDiscTitles().stream().filter(title -> Objects.equals(title.getDisc(), songs.get(position).getDiscNumber())).findFirst();
|
||||
|
||||
if (discTitle.isPresent() && discTitle.get().getDisc() != null && discTitle.get().getTitle() != null && !discTitle.get().getTitle().isEmpty()) {
|
||||
holder.item.discTitleTextView.setText(holder.itemView.getContext().getString(R.string.disc_titlefull, discTitle.get().getDisc().toString() , discTitle.get().getTitle()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Preferences.showItemRating()) {
|
||||
@@ -111,7 +173,8 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
}
|
||||
|
||||
public void setItems(List<Child> songs) {
|
||||
this.songs = songs != null ? songs : Collections.emptyList();
|
||||
this.songsFull = songs != null ? songs : Collections.emptyList();
|
||||
filtering.filter(currentFilter);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@@ -125,6 +188,11 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Filter getFilter() {
|
||||
return filtering;
|
||||
}
|
||||
|
||||
public Child getItem(int id) {
|
||||
return songs.get(id);
|
||||
}
|
||||
@@ -163,4 +231,20 @@ public class SongHorizontalAdapter extends RecyclerView.Adapter<SongHorizontalAd
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public void sort(String order) {
|
||||
switch (order) {
|
||||
case Constants.MEDIA_BY_TITLE:
|
||||
songs.sort(Comparator.comparing(Child::getTitle));
|
||||
break;
|
||||
case Constants.MEDIA_MOST_RECENTLY_STARRED:
|
||||
songs.sort(Comparator.comparing(Child::getStarred, Comparator.nullsLast(Comparator.reverseOrder())));
|
||||
break;
|
||||
case Constants.MEDIA_LEAST_RECENTLY_STARRED:
|
||||
songs.sort(Comparator.comparing(Child::getStarred, Comparator.nullsLast(Comparator.naturalOrder())));
|
||||
break;
|
||||
}
|
||||
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogGithubTempoUpdateBinding;
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class GithubTempoUpdateDialog extends DialogFragment {
|
||||
private final LatestRelease latestRelease;
|
||||
|
||||
public GithubTempoUpdateDialog(LatestRelease latestRelease) {
|
||||
this.latestRelease = latestRelease;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
DialogGithubTempoUpdateBinding bind = DialogGithubTempoUpdateBinding.inflate(getLayoutInflater());
|
||||
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.github_update_dialog_title)
|
||||
.setPositiveButton(R.string.github_update_dialog_positive_button, (dialog, id) -> { })
|
||||
.setNegativeButton(R.string.github_update_dialog_negative_button, (dialog, id) -> { })
|
||||
.setNeutralButton(R.string.github_update_dialog_neutral_button, (dialog, id) -> { });
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
AlertDialog alertDialog = (AlertDialog) Objects.requireNonNull(getDialog());
|
||||
|
||||
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
openLink(latestRelease.getHtmlUrl());
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
|
||||
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setOnClickListener(v -> {
|
||||
Preferences.setTempoUpdateReminder();
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
|
||||
alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
openLink(getString(R.string.support_url));
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
private void openLink(String link) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -81,7 +82,7 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
playlistEditorViewModel.setPlaylistToEdit(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
|
||||
|
||||
if (playlistEditorViewModel.getPlaylistToEdit() != null) {
|
||||
bind.playlistNameTextView.setText(MusicUtil.getReadableString(playlistEditorViewModel.getPlaylistToEdit().getName()));
|
||||
bind.playlistNameTextView.setText(playlistEditorViewModel.getPlaylistToEdit().getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,9 +102,12 @@ public class PlaylistEditorDialog extends DialogFragment {
|
||||
}
|
||||
});
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> Toast.makeText(requireContext(), R.string.playlist_editor_dialog_action_delete_toast, Toast.LENGTH_SHORT).show());
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnLongClickListener(v -> {
|
||||
playlistEditorViewModel.deletePlaylist();
|
||||
dialogDismiss();
|
||||
return false;
|
||||
});
|
||||
|
||||
bind.playlistShareButton.setOnClickListener(view -> {
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.cappielloantonio.tempo.ui.dialog;
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
@@ -28,6 +30,7 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
private String username;
|
||||
private String password;
|
||||
private String server;
|
||||
private String localAddress;
|
||||
private boolean lowSecurity = false;
|
||||
|
||||
@NonNull
|
||||
@@ -69,6 +72,7 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
bind.usernameTextView.setText(loginViewModel.getServerToEdit().getUsername());
|
||||
bind.passwordTextView.setText("");
|
||||
bind.serverTextView.setText(loginViewModel.getServerToEdit().getAddress());
|
||||
bind.localAddressTextView.setText(loginViewModel.getServerToEdit().getLocalAddress());
|
||||
bind.lowSecurityCheckbox.setChecked(loginViewModel.getServerToEdit().isLowSecurity());
|
||||
}
|
||||
} else {
|
||||
@@ -86,9 +90,12 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
}
|
||||
});
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> {
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnClickListener(v -> Toast.makeText(requireContext(), R.string.server_signup_dialog_action_delete_toast, Toast.LENGTH_SHORT).show());
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_NEUTRAL).setOnLongClickListener(v -> {
|
||||
loginViewModel.deleteServer(null);
|
||||
Objects.requireNonNull(getDialog()).dismiss();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,7 +103,8 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
serverName = Objects.requireNonNull(bind.serverNameTextView.getText()).toString().trim();
|
||||
username = Objects.requireNonNull(bind.usernameTextView.getText()).toString().trim();
|
||||
password = bind.lowSecurityCheckbox.isChecked() ? MusicUtil.passwordHexEncoding(Objects.requireNonNull(bind.passwordTextView.getText()).toString()) : Objects.requireNonNull(bind.passwordTextView.getText()).toString();
|
||||
server = Objects.requireNonNull(bind.serverTextView.getText()).toString().trim();
|
||||
server = bind.serverTextView.getText() != null && !bind.serverTextView.getText().toString().trim().isBlank() ? bind.serverTextView.getText().toString().trim() : null;
|
||||
localAddress = bind.localAddressTextView.getText() != null && !bind.localAddressTextView.getText().toString().trim().isBlank() ? bind.localAddressTextView.getText().toString().trim() : null;
|
||||
lowSecurity = bind.lowSecurityCheckbox.isChecked();
|
||||
|
||||
if (TextUtils.isEmpty(serverName)) {
|
||||
@@ -114,6 +122,11 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!TextUtils.isEmpty(localAddress) && !localAddress.matches("^https?://(.*)")) {
|
||||
bind.localAddressTextView.setError(getString(R.string.error_server_prefix));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server.matches("^https?://(.*)")) {
|
||||
bind.serverTextView.setError(getString(R.string.error_server_prefix));
|
||||
return false;
|
||||
@@ -124,6 +137,6 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
|
||||
private void saveServerPreference() {
|
||||
String serverID = loginViewModel.getServerToEdit() != null ? loginViewModel.getServerToEdit().getServerId() : UUID.randomUUID().toString();
|
||||
loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.password, this.server, System.currentTimeMillis(), this.lowSecurity));
|
||||
loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.password, this.server, this.localAddress, System.currentTimeMillis(), this.lowSecurity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class ServerUnreachableDialog extends DialogFragment {
|
||||
});
|
||||
|
||||
alertDialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> {
|
||||
Preferences.setServerUnreachableDatetime(System.currentTimeMillis());
|
||||
Preferences.setServerUnreachableDatetime();
|
||||
alertDialog.dismiss();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.os.Bundle;
|
||||
import android.widget.Button;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogStreamingCacheStorageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.DialogClickCallback;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class StreamingCacheStorageDialog extends DialogFragment {
|
||||
private final DialogClickCallback dialogClickCallback;
|
||||
|
||||
public StreamingCacheStorageDialog(DialogClickCallback dialogClickCallback) {
|
||||
this.dialogClickCallback = dialogClickCallback;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
DialogStreamingCacheStorageBinding bind = DialogStreamingCacheStorageBinding.inflate(getLayoutInflater());
|
||||
|
||||
return new MaterialAlertDialogBuilder(getActivity())
|
||||
.setView(bind.getRoot())
|
||||
.setTitle(R.string.streaming_cache_storage_dialog_title)
|
||||
.setPositiveButton(R.string.streaming_cache_storage_external_dialog_positive_button, null)
|
||||
.setNegativeButton(R.string.streaming_cache_storage_internal_dialog_negative_button, null)
|
||||
.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
setButtonAction();
|
||||
}
|
||||
|
||||
private void setButtonAction() {
|
||||
androidx.appcompat.app.AlertDialog dialog = (androidx.appcompat.app.AlertDialog) getDialog();
|
||||
|
||||
if (dialog != null) {
|
||||
Button positiveButton = dialog.getButton(Dialog.BUTTON_POSITIVE);
|
||||
positiveButton.setOnClickListener(v -> {
|
||||
int currentPreference = Preferences.getStreamingCacheStoragePreference();
|
||||
int newPreference = 1;
|
||||
|
||||
if (currentPreference != newPreference) {
|
||||
Preferences.setStreamingCacheStoragePreference(newPreference);
|
||||
dialogClickCallback.onPositiveClick();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
|
||||
Button negativeButton = dialog.getButton(Dialog.BUTTON_NEGATIVE);
|
||||
negativeButton.setOnClickListener(v -> {
|
||||
int currentPreference = Preferences.getStreamingCacheStoragePreference();
|
||||
int newPreference = 0;
|
||||
|
||||
if (currentPreference != newPreference) {
|
||||
Preferences.setStreamingCacheStoragePreference(newPreference);
|
||||
dialogClickCallback.onNegativeClick();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,12 @@ public class AlbumCatalogueFragment extends Fragment implements ClickCallback {
|
||||
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_added) {
|
||||
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_ADDED);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_album_sort_recently_played) {
|
||||
albumAdapter.sort(Constants.ALBUM_ORDER_BY_RECENTLY_PLAYED);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_album_sort_most_played) {
|
||||
albumAdapter.sort(Constants.ALBUM_ORDER_BY_MOST_PLAYED);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -17,12 +27,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentAlbumListPageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.AlbumListPageViewModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentAlbumListPageBinding bind;
|
||||
@@ -31,6 +43,12 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
private AlbumListPageViewModel albumListPageViewModel;
|
||||
private AlbumHorizontalAdapter albumHorizontalAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -74,7 +92,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
} else if (requireArguments().getParcelable(Constants.ARTIST_OBJECT) != null) {
|
||||
albumListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
|
||||
albumListPageViewModel.title = Constants.ALBUM_FROM_ARTIST;
|
||||
bind.pageTitleLabel.setText(MusicUtil.getReadableString(albumListPageViewModel.artist.getName()));
|
||||
bind.pageTitleLabel.setText(albumListPageViewModel.artist.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +104,10 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.toolbar.setNavigationOnClickListener(v -> {
|
||||
hideKeyboard(v);
|
||||
activity.navController.navigateUp();
|
||||
});
|
||||
|
||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||
if ((bind.albumInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||
@@ -97,6 +118,7 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initAlbumListView() {
|
||||
bind.albumListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.albumListRecyclerView.setHasFixedSize(true);
|
||||
@@ -107,7 +129,99 @@ public class AlbumListPageFragment extends Fragment implements ClickCallback {
|
||||
);
|
||||
|
||||
bind.albumListRecyclerView.setAdapter(albumHorizontalAdapter);
|
||||
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> albumHorizontalAdapter.setItems(albums));
|
||||
albumListPageViewModel.getAlbumList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), albums -> {
|
||||
albumHorizontalAdapter.setItems(albums);
|
||||
setAlbumListPageSubtitle(albums);
|
||||
setAlbumListPageSorter();
|
||||
});
|
||||
|
||||
bind.albumListRecyclerView.setOnTouchListener((v, event) -> {
|
||||
hideKeyboard(v);
|
||||
return false;
|
||||
});
|
||||
|
||||
bind.albumListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_horizontal_album_popup_menu));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.toolbar_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
albumHorizontalAdapter.getFilter().filter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setPadding(-32, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void hideKeyboard(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private void showPopupMenu(View view, int menuResource) {
|
||||
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_name) {
|
||||
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_NAME);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_most_recently_starred) {
|
||||
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_MOST_RECENTLY_STARRED);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_horizontal_album_sort_least_recently_starred) {
|
||||
albumHorizontalAdapter.sort(Constants.ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void setAlbumListPageSubtitle(List<AlbumID3> albums) {
|
||||
switch (albumListPageViewModel.title) {
|
||||
case Constants.ALBUM_RECENTLY_PLAYED:
|
||||
case Constants.ALBUM_MOST_PLAYED:
|
||||
case Constants.ALBUM_RECENTLY_ADDED:
|
||||
bind.pageSubtitleLabel.setText(albums.size() < albumListPageViewModel.maxNumber ?
|
||||
getString(R.string.generic_list_page_count, albums.size()) :
|
||||
getString(R.string.generic_list_page_count_unknown, albumListPageViewModel.maxNumber)
|
||||
);
|
||||
break;
|
||||
case Constants.ALBUM_STARRED:
|
||||
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, albums.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setAlbumListPageSorter() {
|
||||
switch (albumListPageViewModel.title) {
|
||||
case Constants.ALBUM_RECENTLY_PLAYED:
|
||||
case Constants.ALBUM_MOST_PLAYED:
|
||||
case Constants.ALBUM_RECENTLY_ADDED:
|
||||
bind.albumListSortImageView.setVisibility(View.GONE);
|
||||
break;
|
||||
case Constants.ALBUM_STARRED:
|
||||
bind.albumListSortImageView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -34,7 +36,6 @@ import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.AlbumPageViewModel;
|
||||
import com.google.android.material.chip.Chip;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -46,9 +47,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentAlbumPageBinding bind;
|
||||
private MainActivity activity;
|
||||
private AlbumPageViewModel albumPageViewModel;
|
||||
|
||||
private SongHorizontalAdapter songHorizontalAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
|
||||
@Override
|
||||
@@ -105,10 +104,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
|
||||
if (item.getItemId() == R.id.action_download_album) {
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(
|
||||
MappingUtil.mapDownloads(songs),
|
||||
songs.stream().map(Download::new).collect(Collectors.toList())
|
||||
);
|
||||
DownloadUtil.getDownloadTracker(requireContext()).download(MappingUtil.mapDownloads(songs), songs.stream().map(Download::new).collect(Collectors.toList()));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -117,7 +113,7 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void init() {
|
||||
albumPageViewModel.setAlbum(requireArguments().getParcelable(Constants.ALBUM_OBJECT));
|
||||
albumPageViewModel.setAlbum(getViewLifecycleOwner(), requireArguments().getParcelable(Constants.ALBUM_OBJECT));
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
@@ -126,15 +122,36 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
if (activity.getSupportActionBar() != null) {
|
||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
bind.animToolbar.setTitle(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
|
||||
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||
if (bind != null && album != null) {
|
||||
bind.animToolbar.setTitle(album.getName());
|
||||
|
||||
bind.albumNameLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getName()));
|
||||
bind.albumArtistLabel.setText(MusicUtil.getReadableString(albumPageViewModel.getAlbum().getArtist()));
|
||||
bind.albumReleaseYearLabel.setText(albumPageViewModel.getAlbum().getYear() != 0 ? String.valueOf(albumPageViewModel.getAlbum().getYear()) : "");
|
||||
bind.albumSongCountDurationTextview.setText(getString(R.string.album_page_tracks_count_and_duration, albumPageViewModel.getAlbum().getSongCount(), albumPageViewModel.getAlbum().getDuration() != null ? albumPageViewModel.getAlbum().getDuration() / 60 : 0));
|
||||
bind.albumGenresTextview.setText(albumPageViewModel.getAlbum().getGenre());
|
||||
bind.albumNameLabel.setText(album.getName());
|
||||
bind.albumArtistLabel.setText(album.getArtist());
|
||||
bind.albumReleaseYearLabel.setText(album.getYear() != 0 ? String.valueOf(album.getYear()) : "");
|
||||
bind.albumSongCountDurationTextview.setText(getString(R.string.album_page_tracks_count_and_duration, album.getSongCount(), album.getDuration() != null ? album.getDuration() / 60 : 0));
|
||||
bind.albumGenresTextview.setText(album.getGenre());
|
||||
|
||||
if (album.getReleaseDate() != null && album.getOriginalReleaseDate() != null) {
|
||||
bind.albumReleaseYearsTextview.setVisibility(View.VISIBLE);
|
||||
|
||||
if (album.getReleaseDate() == null || album.getOriginalReleaseDate() == null) {
|
||||
bind.albumReleaseYearsTextview.setText(getString(R.string.album_page_release_date_label, album.getReleaseDate() != null ? album.getReleaseDate().getFormattedDate() : album.getOriginalReleaseDate().getFormattedDate()));
|
||||
}
|
||||
|
||||
if (album.getReleaseDate() != null && album.getOriginalReleaseDate() != null) {
|
||||
if (Objects.equals(album.getReleaseDate().getYear(), album.getOriginalReleaseDate().getYear()) && Objects.equals(album.getReleaseDate().getMonth(), album.getOriginalReleaseDate().getMonth()) && Objects.equals(album.getReleaseDate().getDay(), album.getOriginalReleaseDate().getDay())) {
|
||||
bind.albumReleaseYearsTextview.setText(getString(R.string.album_page_release_date_label, album.getReleaseDate().getFormattedDate()));
|
||||
} else {
|
||||
bind.albumReleaseYearsTextview.setText(getString(R.string.album_page_release_dates_label, album.getReleaseDate().getFormattedDate(), album.getOriginalReleaseDate().getFormattedDate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
|
||||
@@ -165,7 +182,15 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
if (albumInfo != null) {
|
||||
if (bind != null) bind.albumNotesTextview.setVisibility(View.VISIBLE);
|
||||
if (bind != null)
|
||||
bind.albumNotesTextview.setText(MusicUtil.getReadableString(albumInfo.getNotes()));
|
||||
bind.albumNotesTextview.setText(MusicUtil.forceReadableString(albumInfo.getNotes()));
|
||||
|
||||
if (bind != null && albumInfo.getLastFmUrl() != null && !albumInfo.getLastFmUrl().isEmpty()) {
|
||||
bind.albumNotesTextview.setOnClickListener(v -> {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(albumInfo.getLastFmUrl()));
|
||||
startActivity(intent);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (bind != null) bind.albumNotesTextview.setVisibility(View.GONE);
|
||||
}
|
||||
@@ -195,20 +220,25 @@ public class AlbumPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initBackCover() {
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), albumPageViewModel.getAlbum().getCoverArtId(), CustomGlideRequest.ResourceType.Album)
|
||||
.build()
|
||||
.into(bind.albumCoverImageView);
|
||||
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||
if (bind != null && album != null) {
|
||||
CustomGlideRequest.Builder.from(requireContext(), album.getCoverArtId(), CustomGlideRequest.ResourceType.Album).build().into(bind.albumCoverImageView);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initSongsView() {
|
||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songRecyclerView.setHasFixedSize(true);
|
||||
albumPageViewModel.getAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||
if (bind != null && album != null) {
|
||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, false, false, album);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
albumPageViewModel.getAlbumSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -16,11 +26,15 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentArtistListPageBinding;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.viewmodel.ArtistListPageViewModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class ArtistListPageFragment extends Fragment implements ClickCallback {
|
||||
private FragmentArtistListPageBinding bind;
|
||||
@@ -30,6 +44,12 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private ArtistHorizontalAdapter artistHorizontalAdapter;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -69,7 +89,10 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.toolbar.setNavigationOnClickListener(v -> {
|
||||
hideKeyboard(v);
|
||||
activity.navController.navigateUp();
|
||||
});
|
||||
|
||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||
if ((bind.artistInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) {
|
||||
@@ -80,18 +103,100 @@ public class ArtistListPageFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initArtistListView() {
|
||||
bind.artistListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.artistListRecyclerView.setHasFixedSize(true);
|
||||
|
||||
artistHorizontalAdapter = new ArtistHorizontalAdapter(this);
|
||||
bind.artistListRecyclerView.setAdapter(artistHorizontalAdapter);
|
||||
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> artistHorizontalAdapter.setItems(artists));
|
||||
artistListPageViewModel.getArtistList(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), artists -> {
|
||||
artistHorizontalAdapter.setItems(artists);
|
||||
setArtistListPageSubtitle(artists);
|
||||
setArtistListPageSorter();
|
||||
});
|
||||
|
||||
bind.artistListRecyclerView.setOnTouchListener((v, event) -> {
|
||||
hideKeyboard(v);
|
||||
return false;
|
||||
});
|
||||
|
||||
bind.artistListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_horizontal_artist_popup_menu));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.toolbar_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
artistHorizontalAdapter.getFilter().filter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setPadding(-32, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void hideKeyboard(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private void showPopupMenu(View view, int menuResource) {
|
||||
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_name) {
|
||||
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_NAME);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_most_recently_starred) {
|
||||
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_MOST_RECENTLY_STARRED);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_horizontal_artist_sort_least_recently_starred) {
|
||||
artistHorizontalAdapter.sort(Constants.ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void setArtistListPageSubtitle(List<ArtistID3> artists) {
|
||||
switch (artistListPageViewModel.title) {
|
||||
case Constants.ARTIST_STARRED:
|
||||
case Constants.ARTIST_DOWNLOADED:
|
||||
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, artists.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setArtistListPageSorter() {
|
||||
switch (artistListPageViewModel.title) {
|
||||
case Constants.ARTIST_STARRED:
|
||||
case Constants.ARTIST_DOWNLOADED:
|
||||
bind.artistListSortImageView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onArtistClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.albumListPageFragment, bundle);
|
||||
Navigation.findNavController(requireView()).navigate(R.id.artistPageFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -110,7 +110,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
if (activity.getSupportActionBar() != null)
|
||||
activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
bind.collapsingToolbar.setTitle(MusicUtil.getReadableString(artistPageViewModel.getArtist().getName()));
|
||||
bind.collapsingToolbar.setTitle(artistPageViewModel.getArtist().getName());
|
||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.collapsingToolbar.setExpandedTitleColor(getResources().getColor(R.color.white, null));
|
||||
}
|
||||
@@ -172,7 +172,7 @@ public class ArtistPageFragment extends Fragment implements ClickCallback {
|
||||
private void initTopSongsView() {
|
||||
bind.mostStreamedSongRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, true, null);
|
||||
bind.mostStreamedSongRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
artistPageViewModel.getArtistTopSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
|
||||
@@ -91,7 +91,7 @@ public class FilterFragment extends Fragment {
|
||||
bind.filterContainer.setVisibility(View.VISIBLE);
|
||||
for (Genre genre : genres) {
|
||||
Chip chip = (Chip) requireActivity().getLayoutInflater().inflate(R.layout.chip_search_filter_genre, null, false);
|
||||
chip.setText(MusicUtil.getReadableString(genre.getGenre()));
|
||||
chip.setText(genre.getGenre());
|
||||
chip.setChecked(filterViewModel.getFilters().contains(genre.getGenre()));
|
||||
chip.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
if (isChecked)
|
||||
|
||||
@@ -105,7 +105,7 @@ public class GenreCatalogueFragment extends Fragment implements ClickCallback {
|
||||
genreCatalogueAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY);
|
||||
bind.genreCatalogueRecyclerView.setAdapter(genreCatalogueAdapter);
|
||||
|
||||
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres));
|
||||
genreCatalogueViewModel.getGenreList().observe(getViewLifecycleOwner(), genres -> genreCatalogueAdapter.setItems(genres) );
|
||||
|
||||
bind.genreCatalogueRecyclerView.setOnTouchListener((v, event) -> {
|
||||
hideKeyboard(v);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -29,8 +30,8 @@ import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.FragmentHomeTabMusicBinding;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.CustomLinearSnapHelper;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.DotsIndicatorDecoration;
|
||||
import com.cappielloantonio.tempo.helper.recyclerview.GridItemDecoration;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.PlaylistCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.HomeSector;
|
||||
import com.cappielloantonio.tempo.service.DownloaderManager;
|
||||
@@ -44,12 +45,13 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumHorizontalAdapter;
|
||||
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.PlaylistHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ShareHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SimilarTrackAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.YearAdapter;
|
||||
import com.cappielloantonio.tempo.ui.dialog.HomeRearrangementDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.PlaylistEditorDialog;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
@@ -62,6 +64,7 @@ import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@UnstableApi
|
||||
public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
@@ -76,6 +79,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
private ArtistAdapter radioArtistAdapter;
|
||||
private ArtistAdapter bestOfArtistAdapter;
|
||||
private SongHorizontalAdapter starredSongAdapter;
|
||||
private SongHorizontalAdapter topSongAdapter;
|
||||
private AlbumHorizontalAdapter starredAlbumAdapter;
|
||||
private ArtistHorizontalAdapter starredArtistAdapter;
|
||||
private AlbumAdapter recentlyAddedAlbumAdapter;
|
||||
@@ -83,7 +87,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
private AlbumAdapter mostPlayedAlbumAdapter;
|
||||
private AlbumHorizontalAdapter newReleasesAlbumAdapter;
|
||||
private YearAdapter yearAdapter;
|
||||
private GridTrackAdapter gridTrackAdapter;
|
||||
private PlaylistHorizontalAdapter playlistHorizontalAdapter;
|
||||
private ShareHorizontalAdapter shareHorizontalAdapter;
|
||||
|
||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||
@@ -119,7 +123,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
initNewReleasesView();
|
||||
initYearSongView();
|
||||
initRecentAddedAlbumView();
|
||||
initGridView();
|
||||
initTopSongsView();
|
||||
initPinnedPlaylistsView();
|
||||
initSharesView();
|
||||
initHomeReorganizer();
|
||||
|
||||
@@ -253,6 +258,8 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
homeViewModel.refreshShares(getViewLifecycleOwner());
|
||||
return true;
|
||||
});
|
||||
|
||||
bind.gridTracksPreTextView.setOnClickListener(view -> showPopupMenu(view, R.menu.filter_top_songs_popup_menu));
|
||||
}
|
||||
|
||||
private void initSyncStarredView() {
|
||||
@@ -404,30 +411,42 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
artistRadioSnapHelper.attachToRecyclerView(bind.radioArtistRecyclerView);
|
||||
}
|
||||
|
||||
private void initGridView() {
|
||||
private void initTopSongsView() {
|
||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_TOP_SONGS)) return;
|
||||
|
||||
bind.gridTracksRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
|
||||
bind.gridTracksRecyclerView.addItemDecoration(new GridItemDecoration(3, 8, false));
|
||||
bind.gridTracksRecyclerView.setHasFixedSize(true);
|
||||
bind.topSongsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
gridTrackAdapter = new GridTrackAdapter(this);
|
||||
bind.gridTracksRecyclerView.setAdapter(gridTrackAdapter);
|
||||
topSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.topSongsRecyclerView.setAdapter(topSongAdapter);
|
||||
homeViewModel.getChronologySample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||
if (chronologies == null || chronologies.isEmpty()) {
|
||||
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);
|
||||
if (bind != null)
|
||||
bind.topSongsRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), UIUtil.getSpanCount(chronologies.size(), 5), GridLayoutManager.HORIZONTAL, false));
|
||||
|
||||
homeViewModel.getDiscoverSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), music -> {
|
||||
if (music != null) {
|
||||
homeViewModel.getGridSongSample(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), chronologies -> {
|
||||
if (chronologies == null || chronologies.isEmpty()) {
|
||||
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);
|
||||
}
|
||||
});
|
||||
List<Child> topSongs = chronologies.stream()
|
||||
.map(cronologia -> (Child) cronologia)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
topSongAdapter.setItems(topSongs);
|
||||
}
|
||||
});
|
||||
|
||||
SnapHelper topTrackSnapHelper = new PagerSnapHelper();
|
||||
topTrackSnapHelper.attachToRecyclerView(bind.topSongsRecyclerView);
|
||||
|
||||
bind.topSongsRecyclerView.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 initStarredTracksView() {
|
||||
@@ -435,7 +454,7 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
bind.starredTracksRecyclerView.setHasFixedSize(true);
|
||||
|
||||
starredSongAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
starredSongAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.starredTracksRecyclerView.setAdapter(starredSongAdapter);
|
||||
homeViewModel.getStarredTracks(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), songs -> {
|
||||
if (songs == null) {
|
||||
@@ -656,6 +675,26 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
recentAddedAlbumSnapHelper.attachToRecyclerView(bind.recentlyAddedAlbumsRecyclerView);
|
||||
}
|
||||
|
||||
private void initPinnedPlaylistsView() {
|
||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_PINNED_PLAYLISTS)) return;
|
||||
|
||||
bind.pinnedPlaylistsRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.pinnedPlaylistsRecyclerView.setHasFixedSize(true);
|
||||
|
||||
playlistHorizontalAdapter = new PlaylistHorizontalAdapter(this);
|
||||
bind.pinnedPlaylistsRecyclerView.setAdapter(playlistHorizontalAdapter);
|
||||
homeViewModel.getPinnedPlaylists(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), playlists -> {
|
||||
if (playlists == null) {
|
||||
if (bind != null) bind.pinnedPlaylistsSector.setVisibility(View.GONE);
|
||||
} else {
|
||||
if (bind != null)
|
||||
bind.pinnedPlaylistsSector.setVisibility(!playlists.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
|
||||
playlistHorizontalAdapter.setItems(playlists);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initSharesView() {
|
||||
if (homeViewModel.checkHomeSectorVisibility(Constants.HOME_SECTOR_SHARED)) return;
|
||||
|
||||
@@ -693,7 +732,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private void initHomeReorganizer() {
|
||||
final Handler handler = new Handler();
|
||||
final Runnable runnable = () -> { if (bind != null) bind.homeSectorRearrangementButton.setVisibility(View.VISIBLE); };
|
||||
final Runnable runnable = () -> {
|
||||
if (bind != null) bind.homeSectorRearrangementButton.setVisibility(View.VISIBLE);
|
||||
};
|
||||
handler.postDelayed(runnable, 5000);
|
||||
|
||||
bind.homeSectorRearrangementButton.setOnClickListener(v -> {
|
||||
@@ -774,6 +815,9 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
case Constants.HOME_SECTOR_RECENTLY_ADDED:
|
||||
bind.homeLinearLayoutContainer.addView(bind.homeRecentlyAddedAlbumsSector);
|
||||
break;
|
||||
case Constants.HOME_SECTOR_PINNED_PLAYLISTS:
|
||||
bind.homeLinearLayoutContainer.addView(bind.pinnedPlaylistsSector);
|
||||
break;
|
||||
case Constants.HOME_SECTOR_SHARED:
|
||||
bind.homeLinearLayoutContainer.addView(bind.sharesSector);
|
||||
break;
|
||||
@@ -784,6 +828,42 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
}
|
||||
|
||||
private void showPopupMenu(View view, int menuResource) {
|
||||
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_last_week_name) {
|
||||
homeViewModel.changeChronologyPeriod(getViewLifecycleOwner(), 0);
|
||||
bind.gridTracksPreTextView.setText(getString(R.string.home_title_last_week));
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_last_month_name) {
|
||||
homeViewModel.changeChronologyPeriod(getViewLifecycleOwner(), 1);
|
||||
bind.gridTracksPreTextView.setText(getString(R.string.home_title_last_month));
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_last_year_name) {
|
||||
homeViewModel.changeChronologyPeriod(getViewLifecycleOwner(), 2);
|
||||
bind.gridTracksPreTextView.setText(getString(R.string.home_title_last_year));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void refreshPlaylistView() {
|
||||
final Handler handler = new Handler();
|
||||
|
||||
final Runnable runnable = () -> {
|
||||
if (getView() != null && bind != null && homeViewModel != null)
|
||||
homeViewModel.getPinnedPlaylists(getViewLifecycleOwner());
|
||||
};
|
||||
|
||||
handler.postDelayed(runnable, 100);
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
mediaBrowserListenableFuture = new MediaBrowser.Builder(requireContext(), new SessionToken(requireContext(), new ComponentName(requireContext(), MediaService.class))).buildAsync();
|
||||
}
|
||||
@@ -882,6 +962,24 @@ public class HomeTabMusicFragment extends Fragment implements ClickCallback {
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaylistClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.playlistPageFragment, bundle);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaylistLongClick(Bundle bundle) {
|
||||
PlaylistEditorDialog dialog = new PlaylistEditorDialog(new PlaylistCallback() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
refreshPlaylistView();
|
||||
}
|
||||
});
|
||||
|
||||
dialog.setArguments(bundle);
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onShareLongClick(Bundle bundle) {
|
||||
Navigation.findNavController(requireView()).navigate(R.id.shareBottomSheetDialog, bundle);
|
||||
|
||||
@@ -117,12 +117,13 @@ public class LoginFragment extends Fragment implements ClickCallback {
|
||||
@Override
|
||||
public void onServerClick(Bundle bundle) {
|
||||
Server server = bundle.getParcelable("server_object");
|
||||
saveServerPreference(server.getServerId(), server.getAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
|
||||
saveServerPreference(server.getServerId(), server.getAddress(), server.getLocalAddress(), server.getUsername(), server.getPassword(), server.isLowSecurity());
|
||||
|
||||
SystemRepository systemRepository = new SystemRepository();
|
||||
systemRepository.checkUserCredential(new SystemCallback() {
|
||||
@Override
|
||||
public void onError(Exception exception) {
|
||||
Preferences.switchInUseServerAddress();
|
||||
resetServerPreference();
|
||||
Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
@@ -141,9 +142,10 @@ public class LoginFragment extends Fragment implements ClickCallback {
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
private void saveServerPreference(String serverId, String server, String user, String password, boolean isLowSecurity) {
|
||||
private void saveServerPreference(String serverId, String server, String localAddress, String user, String password, boolean isLowSecurity) {
|
||||
Preferences.setServerId(serverId);
|
||||
Preferences.setServer(server);
|
||||
Preferences.setLocalAddress(localAddress);
|
||||
Preferences.setUser(user);
|
||||
Preferences.setPassword(password);
|
||||
Preferences.setLowSecurity(isLowSecurity);
|
||||
|
||||
@@ -156,11 +156,12 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||
private void setMetadata(MediaMetadata mediaMetadata) {
|
||||
if (mediaMetadata.extras != null) {
|
||||
playerBottomSheetViewModel.setLiveMedia(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("id"));
|
||||
playerBottomSheetViewModel.setLiveAlbum(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("albumId"));
|
||||
playerBottomSheetViewModel.setLiveArtist(getViewLifecycleOwner(), mediaMetadata.extras.getString("type"), mediaMetadata.extras.getString("artistId"));
|
||||
playerBottomSheetViewModel.setLiveDescription(mediaMetadata.extras.getString("description", null));
|
||||
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("title")));
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(MusicUtil.getReadableString(mediaMetadata.extras.getString("artist")));
|
||||
bind.playerHeaderLayout.playerHeaderMediaTitleLabel.setText(mediaMetadata.extras.getString("title"));
|
||||
bind.playerHeaderLayout.playerHeaderMediaArtistLabel.setText(mediaMetadata.extras.getString("artist"));
|
||||
|
||||
CustomGlideRequest.Builder
|
||||
.from(requireContext(), mediaMetadata.extras.getString("coverArtId"), CustomGlideRequest.ResourceType.Song)
|
||||
|
||||
@@ -76,6 +76,7 @@ public class PlayerControllerFragment extends Fragment {
|
||||
initQuickActionView();
|
||||
initCoverLyricsSlideView();
|
||||
initMediaListenable();
|
||||
initMediaLabelButton();
|
||||
initArtistLabelButton();
|
||||
|
||||
return view;
|
||||
@@ -163,8 +164,8 @@ public class PlayerControllerFragment extends Fragment {
|
||||
}
|
||||
|
||||
private void setMetadata(MediaMetadata mediaMetadata) {
|
||||
playerMediaTitleLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.title)));
|
||||
playerArtistNameLabel.setText(MusicUtil.getReadableString(String.valueOf(mediaMetadata.artist)));
|
||||
playerMediaTitleLabel.setText(String.valueOf(mediaMetadata.title));
|
||||
playerArtistNameLabel.setText(String.valueOf(mediaMetadata.artist));
|
||||
|
||||
playerMediaTitleLabel.setSelected(true);
|
||||
playerArtistNameLabel.setSelected(true);
|
||||
@@ -299,6 +300,19 @@ public class PlayerControllerFragment extends Fragment {
|
||||
});
|
||||
}
|
||||
|
||||
private void initMediaLabelButton() {
|
||||
playerBottomSheetViewModel.getLiveAlbum().observe(getViewLifecycleOwner(), album -> {
|
||||
if (album != null) {
|
||||
playerMediaTitleLabel.setOnClickListener(view -> {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putParcelable(Constants.ALBUM_OBJECT, album);
|
||||
NavHostFragment.findNavController(this).navigate(R.id.albumPageFragment, bundle);
|
||||
activity.collapseBottomSheetDelayed();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initArtistLabelButton() {
|
||||
playerBottomSheetViewModel.getLiveArtist().observe(getViewLifecycleOwner(), artist -> {
|
||||
if (artist != null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -8,6 +9,9 @@ import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -58,8 +62,29 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.playlist_page_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
songHorizontalAdapter.getFilter().filter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setPadding(-32, 0, 0, 0);
|
||||
|
||||
initMenuOption(menu);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -115,6 +140,12 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_pin_playlist) {
|
||||
playlistPageViewModel.setPinned(true);
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_unpin_playlist) {
|
||||
playlistPageViewModel.setPinned(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -124,6 +155,13 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
playlistPageViewModel.setPlaylist(requireArguments().getParcelable(Constants.PLAYLIST_OBJECT));
|
||||
}
|
||||
|
||||
private void initMenuOption(Menu menu) {
|
||||
playlistPageViewModel.isPinned(getViewLifecycleOwner()).observe(getViewLifecycleOwner(), isPinned -> {
|
||||
menu.findItem(R.id.action_unpin_playlist).setVisible(isPinned);
|
||||
menu.findItem(R.id.action_pin_playlist).setVisible(!isPinned);
|
||||
});
|
||||
}
|
||||
|
||||
private void initAppBar() {
|
||||
activity.setSupportActionBar(bind.animToolbar);
|
||||
|
||||
@@ -132,17 +170,25 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
bind.animToolbar.setTitle(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
|
||||
bind.animToolbar.setTitle(playlistPageViewModel.getPlaylist().getName());
|
||||
|
||||
bind.playlistNameLabel.setText(MusicUtil.getReadableString(playlistPageViewModel.getPlaylist().getName()));
|
||||
bind.playlistNameLabel.setText(playlistPageViewModel.getPlaylist().getName());
|
||||
bind.playlistSongCountLabel.setText(getString(R.string.playlist_song_count, playlistPageViewModel.getPlaylist().getSongCount()));
|
||||
bind.playlistDurationLabel.setText(getString(R.string.playlist_duration, MusicUtil.getReadableDurationString(playlistPageViewModel.getPlaylist().getDuration(), false)));
|
||||
|
||||
bind.animToolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.animToolbar.setNavigationOnClickListener(v -> {
|
||||
hideKeyboard(v);
|
||||
activity.navController.navigateUp();
|
||||
});
|
||||
|
||||
Objects.requireNonNull(bind.animToolbar.getOverflowIcon()).setTint(requireContext().getResources().getColor(R.color.titleTextColor, null));
|
||||
}
|
||||
|
||||
private void hideKeyboard(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private void initMusicButton() {
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (bind != null) {
|
||||
@@ -200,7 +246,7 @@ public class PlaylistPageFragment extends Fragment implements ClickCallback {
|
||||
bind.songRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.songRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
|
||||
playlistPageViewModel.getPlaylistSongLiveList().observe(getViewLifecycleOwner(), songs -> songHorizontalAdapter.setItems(songs));
|
||||
|
||||
@@ -90,9 +90,9 @@ public class PodcastChannelPageFragment extends Fragment implements ClickCallbac
|
||||
activity.getSupportActionBar().setDisplayShowHomeEnabled(true);
|
||||
}
|
||||
|
||||
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||
bind.toolbar.setTitle(podcastChannelPageViewModel.getPodcastChannel().getTitle());
|
||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.toolbar.setTitle(MusicUtil.getReadableString(podcastChannelPageViewModel.getPodcastChannel().getTitle()));
|
||||
bind.toolbar.setTitle(podcastChannelPageViewModel.getPodcastChannel().getTitle());
|
||||
}
|
||||
|
||||
private void initPodcastChannelInfo() {
|
||||
|
||||
@@ -112,7 +112,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
bind.searchResultTracksRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.searchResultTracksRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.searchResultTracksRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
import android.content.Intent;
|
||||
import android.media.audiofx.AudioEffect;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -30,6 +31,8 @@ import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DeleteDownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.DownloadStorageDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StarredSyncDialog;
|
||||
import com.cappielloantonio.tempo.ui.dialog.StreamingCacheStorageDialog;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SettingViewModel;
|
||||
@@ -82,14 +85,17 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
super.onResume();
|
||||
|
||||
checkEqualizer();
|
||||
checkCacheStorage();
|
||||
checkStorage();
|
||||
|
||||
setStreamingCacheSize();
|
||||
setAppLanguage();
|
||||
setVersion();
|
||||
|
||||
actionLogout();
|
||||
actionScan();
|
||||
actionSyncStarredTracks();
|
||||
actionChangeStreamingCacheStorage();
|
||||
actionChangeDownloadStorage();
|
||||
actionDeleteDownloadStorage();
|
||||
actionKeepScreenOn();
|
||||
@@ -132,6 +138,22 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void checkCacheStorage() {
|
||||
Preference storage = findPreference("streaming_cache_storage");
|
||||
|
||||
if (storage == null) return;
|
||||
|
||||
try {
|
||||
if (requireContext().getExternalFilesDirs(null)[1] == null) {
|
||||
storage.setVisible(false);
|
||||
} else {
|
||||
storage.setSummary(Preferences.getDownloadStoragePreference() == 0 ? R.string.download_storage_internal_dialog_negative_button : R.string.download_storage_external_dialog_positive_button);
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
storage.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStorage() {
|
||||
Preference storage = findPreference("download_storage");
|
||||
|
||||
@@ -148,6 +170,26 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
}
|
||||
}
|
||||
|
||||
private void setStreamingCacheSize() {
|
||||
ListPreference streamingCachePreference = findPreference("streaming_cache_size");
|
||||
|
||||
if (streamingCachePreference != null) {
|
||||
streamingCachePreference.setSummaryProvider(new Preference.SummaryProvider<ListPreference>() {
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence provideSummary(@NonNull ListPreference preference) {
|
||||
CharSequence entry = preference.getEntry();
|
||||
|
||||
if (entry == null) return null;
|
||||
|
||||
long currentSizeMb = DownloadUtil.getStreamingCacheSize(requireActivity()) / (1024 * 1024);
|
||||
|
||||
return getString(R.string.settings_summary_streaming_cache_size, entry, String.valueOf(currentSizeMb));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setAppLanguage() {
|
||||
ListPreference localePref = (ListPreference) findPreference("language");
|
||||
|
||||
@@ -190,7 +232,8 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
|
||||
@Override
|
||||
public void onSuccess(boolean isScanning, long count) {
|
||||
getScanStatus();
|
||||
findPreference("scan_library").setSummary("Scanning: counting " + count + " tracks");
|
||||
if (isScanning) getScanStatus();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -210,6 +253,24 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeStreamingCacheStorage() {
|
||||
findPreference("streaming_cache_storage").setOnPreferenceClickListener(preference -> {
|
||||
StreamingCacheStorageDialog dialog = new StreamingCacheStorageDialog(new DialogClickCallback() {
|
||||
@Override
|
||||
public void onPositiveClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_external_dialog_positive_button);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNegativeClick() {
|
||||
findPreference("streaming_cache_storage").setSummary(R.string.streaming_cache_storage_internal_dialog_negative_button);
|
||||
}
|
||||
});
|
||||
dialog.show(activity.getSupportFragmentManager(), null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private void actionChangeDownloadStorage() {
|
||||
findPreference("download_storage").setOnPreferenceClickListener(preference -> {
|
||||
DownloadStorageDialog dialog = new DownloadStorageDialog(new DialogClickCallback() {
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.SearchView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
@@ -22,14 +32,15 @@ import com.cappielloantonio.tempo.helper.recyclerview.PaginationScrollListener;
|
||||
import com.cappielloantonio.tempo.interfaces.ClickCallback;
|
||||
import com.cappielloantonio.tempo.service.MediaManager;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SongListPageViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
@@ -45,6 +56,12 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
private boolean isLoading = true;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
activity = (MainActivity) getActivity();
|
||||
@@ -95,13 +112,13 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRE) != null) {
|
||||
songListPageViewModel.title = Constants.MEDIA_BY_GENRE;
|
||||
songListPageViewModel.genre = requireArguments().getParcelable(Constants.GENRE_OBJECT);
|
||||
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.genre.getGenre());
|
||||
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.genre.getGenre()));
|
||||
songListPageViewModel.toolbarTitle = songListPageViewModel.genre.getGenre();
|
||||
bind.pageTitleLabel.setText(songListPageViewModel.genre.getGenre());
|
||||
} else if (requireArguments().getString(Constants.MEDIA_BY_ARTIST) != null) {
|
||||
songListPageViewModel.title = Constants.MEDIA_BY_ARTIST;
|
||||
songListPageViewModel.artist = requireArguments().getParcelable(Constants.ARTIST_OBJECT);
|
||||
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName()));
|
||||
bind.pageTitleLabel.setText(getString(R.string.song_list_page_top, MusicUtil.getReadableString(songListPageViewModel.artist.getName())));
|
||||
songListPageViewModel.toolbarTitle = getString(R.string.song_list_page_top, songListPageViewModel.artist.getName());
|
||||
bind.pageTitleLabel.setText(getString(R.string.song_list_page_top, songListPageViewModel.artist.getName()));
|
||||
} else if (requireArguments().getString(Constants.MEDIA_BY_GENRES) != null) {
|
||||
songListPageViewModel.title = Constants.MEDIA_BY_GENRES;
|
||||
songListPageViewModel.filters = requireArguments().getStringArrayList("filters_list");
|
||||
@@ -124,8 +141,8 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
} else if (requireArguments().getParcelable(Constants.ALBUM_OBJECT) != null) {
|
||||
songListPageViewModel.album = requireArguments().getParcelable(Constants.ALBUM_OBJECT);
|
||||
songListPageViewModel.title = Constants.MEDIA_FROM_ALBUM;
|
||||
songListPageViewModel.toolbarTitle = MusicUtil.getReadableString(songListPageViewModel.album.getName());
|
||||
bind.pageTitleLabel.setText(MusicUtil.getReadableString(songListPageViewModel.album.getName()));
|
||||
songListPageViewModel.toolbarTitle = songListPageViewModel.album.getName();
|
||||
bind.pageTitleLabel.setText(songListPageViewModel.album.getName());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +155,10 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
if (bind != null)
|
||||
bind.toolbar.setNavigationOnClickListener(v -> activity.navController.navigateUp());
|
||||
bind.toolbar.setNavigationOnClickListener(v -> {
|
||||
hideKeyboard(v);
|
||||
activity.navController.navigateUp();
|
||||
});
|
||||
|
||||
if (bind != null)
|
||||
bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> {
|
||||
@@ -153,6 +173,8 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
private void initButtons() {
|
||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
if (bind != null) {
|
||||
setSongListPageSorter();
|
||||
|
||||
bind.songListShuffleImageView.setOnClickListener(v -> {
|
||||
Collections.shuffle(songs);
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, songs.subList(0, Math.min(25, songs.size())), 0);
|
||||
@@ -162,15 +184,17 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
private void initSongListView() {
|
||||
bind.songListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
|
||||
bind.songListRecyclerView.setHasFixedSize(true);
|
||||
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false);
|
||||
songHorizontalAdapter = new SongHorizontalAdapter(this, true, false, null);
|
||||
bind.songListRecyclerView.setAdapter(songHorizontalAdapter);
|
||||
songListPageViewModel.getSongList().observe(getViewLifecycleOwner(), songs -> {
|
||||
isLoading = false;
|
||||
songHorizontalAdapter.setItems(songs);
|
||||
setSongListPageSubtitle(songs);
|
||||
});
|
||||
|
||||
bind.songListRecyclerView.addOnScrollListener(new PaginationScrollListener((LinearLayoutManager) bind.songListRecyclerView.getLayoutManager()) {
|
||||
@@ -185,6 +209,101 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
return isLoading;
|
||||
}
|
||||
});
|
||||
|
||||
bind.songListRecyclerView.setOnTouchListener((v, event) -> {
|
||||
hideKeyboard(v);
|
||||
return false;
|
||||
});
|
||||
|
||||
bind.songListSortImageView.setOnClickListener(view -> showPopupMenu(view, R.menu.sort_song_popup_menu));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.toolbar_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
searchView.clearFocus();
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
songHorizontalAdapter.getFilter().filter(newText);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
searchView.setPadding(-32, 0, 0, 0);
|
||||
}
|
||||
|
||||
private void hideKeyboard(View view) {
|
||||
InputMethodManager imm = (InputMethodManager) requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
|
||||
}
|
||||
|
||||
private void showPopupMenu(View view, int menuResource) {
|
||||
PopupMenu popup = new PopupMenu(requireContext(), view);
|
||||
popup.getMenuInflater().inflate(menuResource, popup.getMenu());
|
||||
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
if (menuItem.getItemId() == R.id.menu_song_sort_name) {
|
||||
songHorizontalAdapter.sort(Constants.MEDIA_BY_TITLE);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_song_sort_most_recently_starred) {
|
||||
songHorizontalAdapter.sort(Constants.MEDIA_MOST_RECENTLY_STARRED);
|
||||
return true;
|
||||
} else if (menuItem.getItemId() == R.id.menu_song_sort_least_recently_starred) {
|
||||
songHorizontalAdapter.sort(Constants.MEDIA_LEAST_RECENTLY_STARRED);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
|
||||
private void setSongListPageSubtitle(List<Child> children) {
|
||||
switch (songListPageViewModel.title) {
|
||||
case Constants.MEDIA_BY_GENRE:
|
||||
bind.pageSubtitleLabel.setText(children.size() < songListPageViewModel.maxNumberByGenre ?
|
||||
getString(R.string.generic_list_page_count, children.size()) :
|
||||
getString(R.string.generic_list_page_count_unknown, songListPageViewModel.maxNumberByGenre)
|
||||
);
|
||||
break;
|
||||
case Constants.MEDIA_BY_YEAR:
|
||||
bind.pageSubtitleLabel.setText(children.size() < songListPageViewModel.maxNumberByYear ?
|
||||
getString(R.string.generic_list_page_count, children.size()) :
|
||||
getString(R.string.generic_list_page_count_unknown, songListPageViewModel.maxNumberByYear)
|
||||
);
|
||||
break;
|
||||
case Constants.MEDIA_BY_ARTIST:
|
||||
case Constants.MEDIA_BY_GENRES:
|
||||
case Constants.MEDIA_STARRED:
|
||||
bind.pageSubtitleLabel.setText(getString(R.string.generic_list_page_count, children.size()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void setSongListPageSorter() {
|
||||
switch (songListPageViewModel.title) {
|
||||
case Constants.MEDIA_BY_GENRE:
|
||||
case Constants.MEDIA_BY_YEAR:
|
||||
bind.songListSortImageView.setVisibility(View.GONE);
|
||||
break;
|
||||
case Constants.MEDIA_BY_ARTIST:
|
||||
case Constants.MEDIA_BY_GENRES:
|
||||
case Constants.MEDIA_STARRED:
|
||||
bind.songListSortImageView.setVisibility(View.VISIBLE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
@@ -197,6 +316,7 @@ public class SongListPageFragment extends Fragment implements ClickCallback {
|
||||
|
||||
@Override
|
||||
public void onMediaClick(Bundle bundle) {
|
||||
hideKeyboard(requireView());
|
||||
MediaManager.startQueue(mediaBrowserListenableFuture, bundle.getParcelableArrayList(Constants.TRACKS_OBJECT), bundle.getInt(Constants.ITEM_POSITION));
|
||||
activity.setBottomSheetInPeek(true);
|
||||
}
|
||||
|
||||
@@ -92,11 +92,11 @@ public class AlbumBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
.into(coverAlbum);
|
||||
|
||||
TextView titleAlbum = view.findViewById(R.id.album_title_text_view);
|
||||
titleAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getName()));
|
||||
titleAlbum.setText(albumBottomSheetViewModel.getAlbum().getName());
|
||||
titleAlbum.setSelected(true);
|
||||
|
||||
TextView artistAlbum = view.findViewById(R.id.album_artist_text_view);
|
||||
artistAlbum.setText(MusicUtil.getReadableString(albumBottomSheetViewModel.getAlbum().getArtist()));
|
||||
artistAlbum.setText(albumBottomSheetViewModel.getAlbum().getArtist());
|
||||
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(albumBottomSheetViewModel.getAlbum().getStarred() != null);
|
||||
|
||||
@@ -75,7 +75,7 @@ public class ArtistBottomSheetDialog extends BottomSheetDialogFragment implement
|
||||
.into(coverArtist);
|
||||
|
||||
TextView nameArtist = view.findViewById(R.id.song_title_text_view);
|
||||
nameArtist.setText(MusicUtil.getReadableString(artistBottomSheetViewModel.getArtist().getName()));
|
||||
nameArtist.setText(artistBottomSheetViewModel.getArtist().getName());
|
||||
nameArtist.setSelected(true);
|
||||
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
|
||||
@@ -82,11 +82,11 @@ public class DownloadedBottomSheetDialog extends BottomSheetDialogFragment imple
|
||||
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.setText(this.groupTitle);
|
||||
groupTitleView.setSelected(true);
|
||||
|
||||
TextView groupSubtitleView = view.findViewById(R.id.group_subtitle_text_view);
|
||||
groupSubtitleView.setText(MusicUtil.getReadableString(this.groupSubtitle));
|
||||
groupSubtitleView.setText(this.groupSubtitle);
|
||||
groupSubtitleView.setSelected(true);
|
||||
|
||||
TextView playRandom = view.findViewById(R.id.play_random_text_view);
|
||||
|
||||
@@ -68,7 +68,7 @@ public class PodcastChannelBottomSheetDialog extends BottomSheetDialogFragment i
|
||||
.into(coverPodcast);
|
||||
|
||||
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
|
||||
titlePodcast.setText(MusicUtil.getReadableString(podcastChannelBottomSheetViewModel.getPodcastChannel().getTitle()));
|
||||
titlePodcast.setText(podcastChannelBottomSheetViewModel.getPodcastChannel().getTitle());
|
||||
|
||||
TextView delete = view.findViewById(R.id.delete_text_view);
|
||||
delete.setOnClickListener(v -> {
|
||||
|
||||
@@ -70,7 +70,7 @@ public class PodcastEpisodeBottomSheetDialog extends BottomSheetDialogFragment i
|
||||
.into(coverPodcast);
|
||||
|
||||
TextView titlePodcast = view.findViewById(R.id.podcast_title_text_view);
|
||||
titlePodcast.setText(MusicUtil.getReadableString(podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getTitle()));
|
||||
titlePodcast.setText(podcastEpisodeBottomSheetViewModel.getPodcastEpisode().getTitle());
|
||||
|
||||
titlePodcast.setSelected(true);
|
||||
|
||||
|
||||
@@ -84,12 +84,12 @@ public class SongBottomSheetDialog extends BottomSheetDialogFragment implements
|
||||
.into(coverSong);
|
||||
|
||||
TextView titleSong = view.findViewById(R.id.song_title_text_view);
|
||||
titleSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getTitle()));
|
||||
titleSong.setText(songBottomSheetViewModel.getSong().getTitle());
|
||||
|
||||
titleSong.setSelected(true);
|
||||
|
||||
TextView artistSong = view.findViewById(R.id.song_artist_text_view);
|
||||
artistSong.setText(MusicUtil.getReadableString(songBottomSheetViewModel.getSong().getArtist()));
|
||||
artistSong.setText(songBottomSheetViewModel.getSong().getArtist());
|
||||
|
||||
ToggleButton favoriteToggle = view.findViewById(R.id.button_favorite);
|
||||
favoriteToggle.setChecked(songBottomSheetViewModel.getSong().getStarred() != null);
|
||||
|
||||
@@ -31,11 +31,17 @@ object Constants {
|
||||
const val ALBUM_ORDER_BY_YEAR = "ALBUM_ORDER_BY_YEAR"
|
||||
const val ALBUM_ORDER_BY_RANDOM = "ALBUM_ORDER_BY_RANDOM"
|
||||
const val ALBUM_ORDER_BY_RECENTLY_ADDED = "ALBUM_ORDER_BY_RECENTLY_ADDED"
|
||||
const val ALBUM_ORDER_BY_RECENTLY_PLAYED = "ALBUM_ORDER_BY_RECENTLY_PLAYED"
|
||||
const val ALBUM_ORDER_BY_MOST_PLAYED = "ALBUM_ORDER_BY_MOST_PLAYED"
|
||||
const val ALBUM_ORDER_BY_MOST_RECENTLY_STARRED = "ALBUM_ORDER_BY_MOST_RECENTLY_STARRED"
|
||||
const val ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED = "ALBUM_ORDER_BY_LEAST_RECENTLY_STARRED"
|
||||
|
||||
const val ARTIST_DOWNLOADED = "ARTIST_DOWNLOADED"
|
||||
const val ARTIST_STARRED = "ARTIST_STARRED"
|
||||
const val ARTIST_ORDER_BY_NAME = "ARTIST_ORDER_BY_NAME"
|
||||
const val ARTIST_ORDER_BY_RANDOM = "ARTIST_ORDER_BY_RANDOM"
|
||||
const val ARTIST_ORDER_BY_MOST_RECENTLY_STARRED = "ARTIST_ORDER_BY_MOST_RECENTLY_STARRED"
|
||||
const val ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED = "ARTIST_ORDER_BY_LEAST_RECENTLY_STARRED"
|
||||
|
||||
const val GENRE_ORDER_BY_NAME = "GENRE_ORDER_BY_NAME"
|
||||
const val GENRE_ORDER_BY_RANDOM = "GENRE_ORDER_BY_RANDOM"
|
||||
@@ -74,6 +80,9 @@ object Constants {
|
||||
const val MEDIA_MIX = "MEDIA_MIX"
|
||||
const val MEDIA_CHRONOLOGY = "MEDIA_CHRONOLOGY"
|
||||
const val MEDIA_BEST_OF = "MEDIA_BEST_OF"
|
||||
const val MEDIA_BY_TITLE = "MEDIA_BY_TITLE"
|
||||
const val MEDIA_MOST_RECENTLY_STARRED = "MEDIA_MOST_RECENTLY_STARRED"
|
||||
const val MEDIA_LEAST_RECENTLY_STARRED = "MEDIA_LEAST_RECENTLY_STARRED"
|
||||
|
||||
const val DOWNLOAD_URI = "rest/download"
|
||||
|
||||
@@ -105,5 +114,6 @@ object Constants {
|
||||
const val HOME_SECTOR_MOST_PLAYED = "HOME_SECTOR_MOST_PLAYED"
|
||||
const val HOME_SECTOR_LAST_PLAYED = "HOME_SECTOR_LAST_PLAYED"
|
||||
const val HOME_SECTOR_RECENTLY_ADDED = "HOME_SECTOR_RECENTLY_ADDED"
|
||||
const val HOME_SECTOR_PINNED_PLAYLISTS = "HOME_SECTOR_PINNED_PLAYLISTS"
|
||||
const val HOME_SECTOR_SHARED = "HOME_SECTOR_SHARED"
|
||||
}
|
||||
@@ -8,10 +8,13 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.database.DatabaseProvider;
|
||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSpec;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||
import androidx.media3.datasource.ResolvingDataSource;
|
||||
import androidx.media3.datasource.cache.Cache;
|
||||
import androidx.media3.datasource.cache.CacheDataSource;
|
||||
import androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor;
|
||||
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
||||
import androidx.media3.datasource.cache.SimpleCache;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
@@ -35,20 +38,22 @@ public final class DownloadUtil {
|
||||
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 STREAMING_CACHE_CONTENT_DIRECTORY = "streaming_cache";
|
||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
private static DataSource.Factory dataSourceFactory;
|
||||
private static DataSource.Factory httpDataSourceFactory;
|
||||
private static DatabaseProvider databaseProvider;
|
||||
private static File streamingCacheDirectory;
|
||||
private static File downloadDirectory;
|
||||
private static Cache downloadCache;
|
||||
private static SimpleCache streamingCache;
|
||||
private static DownloadManager downloadManager;
|
||||
private static DownloaderManager downloaderManager;
|
||||
private static DownloadNotificationHelper downloadNotificationHelper;
|
||||
|
||||
public static boolean useExtensionRenderers() {
|
||||
// return true;
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static RenderersFactory buildRenderersFactory(Context context, boolean preferExtensionRenderer) {
|
||||
@@ -65,7 +70,9 @@ public final class DownloadUtil {
|
||||
CookieManager cookieManager = new CookieManager();
|
||||
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
httpDataSourceFactory = new DefaultHttpDataSource
|
||||
.Factory()
|
||||
.setAllowCrossProtocolRedirects(true);
|
||||
}
|
||||
|
||||
return httpDataSourceFactory;
|
||||
@@ -74,8 +81,27 @@ public final class DownloadUtil {
|
||||
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
|
||||
if (dataSourceFactory == null) {
|
||||
context = context.getApplicationContext();
|
||||
|
||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
|
||||
if (Preferences.getStreamingCacheSize() > 0) {
|
||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||
.setCache(getStreamingCache(context))
|
||||
.setUpstreamDataSourceFactory(upstreamFactory);
|
||||
|
||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||
dataSpec -> {
|
||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||
return builder.build();
|
||||
}
|
||||
);
|
||||
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||
} else {
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
}
|
||||
}
|
||||
|
||||
return dataSourceFactory;
|
||||
@@ -108,6 +134,20 @@ public final class DownloadUtil {
|
||||
return downloadCache;
|
||||
}
|
||||
|
||||
private static synchronized SimpleCache getStreamingCache(Context context) {
|
||||
if (streamingCache == null) {
|
||||
File streamingCacheDirectory = new File(getStreamingCacheDirectory(context), STREAMING_CACHE_CONTENT_DIRECTORY);
|
||||
|
||||
streamingCache = new SimpleCache(
|
||||
streamingCacheDirectory,
|
||||
new LeastRecentlyUsedCacheEvictor(Preferences.getStreamingCacheSize() * 1024 * 1024),
|
||||
getDatabaseProvider(context)
|
||||
);
|
||||
}
|
||||
|
||||
return streamingCache;
|
||||
}
|
||||
|
||||
private static synchronized void ensureDownloadManagerInitialized(Context context) {
|
||||
if (downloadManager == null) {
|
||||
downloadManager = new DownloadManager(
|
||||
@@ -130,6 +170,27 @@ public final class DownloadUtil {
|
||||
return databaseProvider;
|
||||
}
|
||||
|
||||
private static synchronized File getStreamingCacheDirectory(Context context) {
|
||||
if (streamingCacheDirectory == null) {
|
||||
if (Preferences.getStreamingCacheStoragePreference() == 0) {
|
||||
streamingCacheDirectory = context.getExternalFilesDirs(null)[0];
|
||||
if (streamingCacheDirectory == null) {
|
||||
streamingCacheDirectory = context.getFilesDir();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
streamingCacheDirectory = context.getExternalFilesDirs(null)[1];
|
||||
} catch (Exception exception) {
|
||||
streamingCacheDirectory = context.getExternalFilesDirs(null)[0];
|
||||
Preferences.setStreamingCacheStoragePreference(0);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return streamingCacheDirectory;
|
||||
}
|
||||
|
||||
private static synchronized File getDownloadDirectory(Context context) {
|
||||
if (downloadDirectory == null) {
|
||||
if (Preferences.getDownloadStoragePreference() == 0) {
|
||||
@@ -187,6 +248,10 @@ public final class DownloadUtil {
|
||||
return files;
|
||||
}
|
||||
|
||||
public static synchronized long getStreamingCacheSize(Context context) {
|
||||
return getStreamingCache(context).getCacheSpace();
|
||||
}
|
||||
|
||||
public static Notification buildGroupSummaryNotification(Context context, String channelId, String groupId, int icon, String title) {
|
||||
return new NotificationCompat.Builder(context, channelId)
|
||||
.setContentTitle(title)
|
||||
|
||||
@@ -74,12 +74,12 @@ public class MappingUtil {
|
||||
.setMediaId(media.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||
.setTitle(media.getTitle())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
@@ -112,12 +112,12 @@ public class MappingUtil {
|
||||
.setMediaId(media.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(media.getTitle()))
|
||||
.setTitle(media.getTitle())
|
||||
.setTrackNumber(media.getTrack() != null ? media.getTrack() : 0)
|
||||
.setDiscNumber(media.getDiscNumber() != null ? media.getDiscNumber() : 0)
|
||||
.setReleaseYear(media.getYear() != null ? media.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(media.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(media.getArtist()))
|
||||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.build()
|
||||
@@ -159,7 +159,7 @@ public class MappingUtil {
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
// .setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build();
|
||||
}
|
||||
@@ -193,10 +193,10 @@ public class MappingUtil {
|
||||
.setMediaId(podcastEpisode.getId())
|
||||
.setMediaMetadata(
|
||||
new MediaMetadata.Builder()
|
||||
.setTitle(MusicUtil.getReadableString(podcastEpisode.getTitle()))
|
||||
.setTitle(podcastEpisode.getTitle())
|
||||
.setReleaseYear(podcastEpisode.getYear() != null ? podcastEpisode.getYear() : 0)
|
||||
.setAlbumTitle(MusicUtil.getReadableString(podcastEpisode.getAlbum()))
|
||||
.setArtist(MusicUtil.getReadableString(podcastEpisode.getArtist()))
|
||||
.setAlbumTitle(podcastEpisode.getAlbum())
|
||||
.setArtist(podcastEpisode.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
|
||||
@@ -157,7 +157,7 @@ public class MusicUtil {
|
||||
}
|
||||
|
||||
public static String getReadableAudioQualityString(Child child) {
|
||||
if (!Preferences.showAudioQuality()) return "";
|
||||
if (!Preferences.showAudioQuality() || child.getBitrate() == null) return "";
|
||||
|
||||
return "•" +
|
||||
" " +
|
||||
@@ -179,14 +179,6 @@ public class MusicUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static String getReadableString(String string) {
|
||||
if (string != null) {
|
||||
return Html.fromHtml(string, Html.FROM_HTML_MODE_COMPACT).toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String getReadableTrackNumber(Context context, Integer trackNumber) {
|
||||
if (trackNumber != null) {
|
||||
return String.valueOf(trackNumber);
|
||||
@@ -195,6 +187,14 @@ public class MusicUtil {
|
||||
return context.getString(R.string.label_placeholder);
|
||||
}
|
||||
|
||||
public static String getReadableString(String string) {
|
||||
if (string != null) {
|
||||
return Html.fromHtml(string, Html.FROM_HTML_MODE_COMPACT).toString();
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String forceReadableString(String string) {
|
||||
if (string != null) {
|
||||
return getReadableString(string)
|
||||
@@ -219,20 +219,6 @@ public class MusicUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static List<String> getReadableStrings(List<String> strings) {
|
||||
List<String> readableStrings = new ArrayList<>();
|
||||
|
||||
if (!strings.isEmpty()) {
|
||||
for (String string : strings) {
|
||||
if (string != null) {
|
||||
readableStrings.add(Html.fromHtml(string, Html.FROM_HTML_MODE_COMPACT).toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return readableStrings;
|
||||
}
|
||||
|
||||
public static String getReadableByteCount(long bytes) {
|
||||
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.cappielloantonio.tempo.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
@@ -11,9 +13,15 @@ public class NetworkUtil {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
|
||||
if (connectivityManager != null) {
|
||||
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
Network network = connectivityManager.getActiveNetwork();
|
||||
|
||||
return networkInfo == null || !networkInfo.isConnected();
|
||||
if (network != null) {
|
||||
NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
|
||||
if (capabilities != null) {
|
||||
return !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) || !capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import android.util.Log
|
||||
import com.cappielloantonio.tempo.App
|
||||
import com.cappielloantonio.tempo.model.HomeSector
|
||||
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension
|
||||
import com.google.gson.Gson
|
||||
|
||||
|
||||
|
||||
|
||||
object Preferences {
|
||||
const val THEME = "theme"
|
||||
private const val SERVER = "server"
|
||||
@@ -20,9 +19,13 @@ object Preferences {
|
||||
private const val SERVER_ID = "server_id"
|
||||
private const val OPEN_SUBSONIC = "open_subsonic"
|
||||
private const val OPEN_SUBSONIC_EXTENSIONS = "open_subsonic_extensions"
|
||||
private const val LOCAL_ADDRESS = "local_address"
|
||||
private const val IN_USE_SERVER_ADDRESS = "in_use_server_address"
|
||||
private const val NEXT_SERVER_SWITCH = "next_server_switch"
|
||||
private const val PLAYBACK_SPEED = "playback_speed"
|
||||
private const val SKIP_SILENCE = "skip_silence"
|
||||
private const val IMAGE_CACHE_SIZE = "image_cache_size"
|
||||
private const val STREAMING_CACHE_SIZE = "streaming_cache_size"
|
||||
private const val IMAGE_SIZE = "image_size"
|
||||
private const val MAX_BITRATE_WIFI = "max_bitrate_wifi"
|
||||
private const val MAX_BITRATE_MOBILE = "max_bitrate_mobile"
|
||||
@@ -41,6 +44,7 @@ object Preferences {
|
||||
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||
private const val STREAMING_CACHE_STORAGE = "streaming_cache_storage"
|
||||
private const val DOWNLOAD_STORAGE = "download_storage"
|
||||
private const val DEFAULT_DOWNLOAD_VIEW_TYPE = "default_download_view_type"
|
||||
private const val AUDIO_TRANSCODE_DOWNLOAD = "audio_transcode_download"
|
||||
@@ -57,6 +61,9 @@ object Preferences {
|
||||
private const val AUDIO_QUALITY_PER_ITEM = "audio_quality_per_item"
|
||||
private const val HOME_SECTOR_LIST = "home_sector_list"
|
||||
private const val RATING_PER_ITEM = "rating_per_item"
|
||||
private const val NEXT_UPDATE_CHECK = "next_update_check"
|
||||
private const val CONTINUOUS_PLAY = "continuous_play"
|
||||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
|
||||
|
||||
@JvmStatic
|
||||
@@ -149,6 +156,46 @@ object Preferences {
|
||||
App.getInstance().preferences.edit().putString(OPEN_SUBSONIC_EXTENSIONS, Gson().toJson(extension)).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLocalAddress(): String? {
|
||||
return App.getInstance().preferences.getString(LOCAL_ADDRESS, null)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setLocalAddress(address: String?) {
|
||||
App.getInstance().preferences.edit().putString(LOCAL_ADDRESS, address).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getInUseServerAddress(): String? {
|
||||
return App.getInstance().preferences.getString(IN_USE_SERVER_ADDRESS, null)
|
||||
?.takeIf { it.isNotBlank() }
|
||||
?: getServer()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isInUseServerAddressLocal(): Boolean {
|
||||
return getInUseServerAddress() == getLocalAddress()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun switchInUseServerAddress() {
|
||||
val inUseAddress = if (getInUseServerAddress() == getServer()) getLocalAddress() else getServer()
|
||||
App.getInstance().preferences.edit().putString(IN_USE_SERVER_ADDRESS, inUseAddress).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isServerSwitchable(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
NEXT_SERVER_SWITCH, 0
|
||||
) + 15000 < System.currentTimeMillis() && !getServer().isNullOrEmpty() && !getLocalAddress().isNullOrEmpty()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setServerSwitchableTimer() {
|
||||
App.getInstance().preferences.edit().putLong(NEXT_SERVER_SWITCH, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun askForOptimization(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||
@@ -189,6 +236,11 @@ object Preferences {
|
||||
return App.getInstance().preferences.getString(IMAGE_SIZE, "-1")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getStreamingCacheSize(): Long {
|
||||
return App.getInstance().preferences.getString(STREAMING_CACHE_SIZE, "256")!!.toLong()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getMaxBitrateWifi(): String {
|
||||
return App.getInstance().preferences.getString(MAX_BITRATE_WIFI, "0")!!
|
||||
@@ -222,7 +274,7 @@ object Preferences {
|
||||
@JvmStatic
|
||||
fun setDataSavingMode(isDataSavingModeEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(DATA_SAVING_MODE, isDataSavingModeEnabled)
|
||||
.apply()
|
||||
.apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -233,20 +285,20 @@ object Preferences {
|
||||
@JvmStatic
|
||||
fun setStarredSyncEnabled(isStarredSyncEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(
|
||||
SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, isStarredSyncEnabled
|
||||
SYNC_STARRED_TRACKS_FOR_OFFLINE_USE, isStarredSyncEnabled
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showServerUnreachableDialog(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
SERVER_UNREACHABLE, 0
|
||||
) + 360000 < System.currentTimeMillis()
|
||||
SERVER_UNREACHABLE, 0
|
||||
) + 86400000 < System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setServerUnreachableDatetime(datetime: Long) {
|
||||
App.getInstance().preferences.edit().putLong(SERVER_UNREACHABLE, datetime).apply()
|
||||
fun setServerUnreachableDatetime() {
|
||||
App.getInstance().preferences.edit().putLong(SERVER_UNREACHABLE, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@@ -304,6 +356,19 @@ object Preferences {
|
||||
return App.getInstance().preferences.getBoolean(AUDIO_TRANSCODE_PRIORITY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getStreamingCacheStoragePreference(): Int {
|
||||
return App.getInstance().preferences.getString(STREAMING_CACHE_STORAGE, "0")!!.toInt()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setStreamingCacheStoragePreference(streamingCachePreference: Int) {
|
||||
return App.getInstance().preferences.edit().putString(
|
||||
STREAMING_CACHE_STORAGE,
|
||||
streamingCachePreference.toString()
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getDownloadStoragePreference(): Int {
|
||||
return App.getInstance().preferences.getString(DOWNLOAD_STORAGE, "0")!!.toInt()
|
||||
@@ -312,24 +377,24 @@ object Preferences {
|
||||
@JvmStatic
|
||||
fun setDownloadStoragePreference(storagePreference: Int) {
|
||||
return App.getInstance().preferences.edit().putString(
|
||||
DOWNLOAD_STORAGE,
|
||||
storagePreference.toString()
|
||||
DOWNLOAD_STORAGE,
|
||||
storagePreference.toString()
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getDefaultDownloadViewType(): String {
|
||||
return App.getInstance().preferences.getString(
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
Constants.DOWNLOAD_TYPE_TRACK
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
Constants.DOWNLOAD_TYPE_TRACK
|
||||
)!!
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setDefaultDownloadViewType(viewType: String) {
|
||||
return App.getInstance().preferences.edit().putString(
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
viewType
|
||||
DEFAULT_DOWNLOAD_VIEW_TYPE,
|
||||
viewType
|
||||
).apply()
|
||||
}
|
||||
|
||||
@@ -402,4 +467,33 @@ object Preferences {
|
||||
fun showItemRating(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(RATING_PER_ITEM, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showTempoUpdateDialog(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
NEXT_UPDATE_CHECK, 0
|
||||
) + 86400000 < System.currentTimeMillis()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setTempoUpdateReminder() {
|
||||
App.getInstance().preferences.edit().putLong(NEXT_UPDATE_CHECK, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isContinuousPlayEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(CONTINUOUS_PLAY, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setLastInstantMix() {
|
||||
App.getInstance().preferences.edit().putLong(LAST_INSTANT_MIX, System.currentTimeMillis()).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isInstantMixUsable(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
LAST_INSTANT_MIX, 0
|
||||
) + 5000 < System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DataSpec
|
||||
import androidx.media3.datasource.TransferListener
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
import androidx.media3.datasource.cache.ContentMetadata
|
||||
|
||||
@UnstableApi
|
||||
class StreamingCacheDataSource private constructor(
|
||||
private val cacheDataSource: CacheDataSource,
|
||||
): DataSource {
|
||||
private val TAG = "StreamingCacheDataSource"
|
||||
|
||||
private var currentDataSpec: DataSpec? = null
|
||||
|
||||
class Factory(private val cacheDatasourceFactory: CacheDataSource.Factory): DataSource.Factory {
|
||||
override fun createDataSource(): DataSource {
|
||||
return StreamingCacheDataSource(cacheDatasourceFactory.createDataSource())
|
||||
}
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, length: Int): Int {
|
||||
return cacheDataSource.read(buffer, offset, length)
|
||||
}
|
||||
|
||||
override fun addTransferListener(transferListener: TransferListener) {
|
||||
return cacheDataSource.addTransferListener(transferListener)
|
||||
}
|
||||
|
||||
override fun open(dataSpec: DataSpec): Long {
|
||||
val ret = cacheDataSource.open(dataSpec)
|
||||
currentDataSpec = dataSpec
|
||||
return ret
|
||||
}
|
||||
|
||||
override fun getUri(): Uri? {
|
||||
return cacheDataSource.uri
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
cacheDataSource.close()
|
||||
|
||||
val dataSpec = currentDataSpec
|
||||
|
||||
if (dataSpec != null) {
|
||||
val cacheKey = cacheDataSource.cacheKeyFactory.buildCacheKey(dataSpec)
|
||||
val contentLength = ContentMetadata.getContentLength(cacheDataSource.cache.getContentMetadata(cacheKey));
|
||||
|
||||
if (contentLength == C.LENGTH_UNSET.toLong()) {
|
||||
Log.d(TAG, "Removing partial cache for $cacheKey")
|
||||
cacheDataSource.cache.removeResource(cacheKey)
|
||||
} else {
|
||||
Log.d(TAG, "Key $cacheKey has been fully cached")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,8 @@ public class AlbumListPageViewModel extends AndroidViewModel {
|
||||
|
||||
private MutableLiveData<List<AlbumID3>> albumList;
|
||||
|
||||
public int maxNumber = 500;
|
||||
|
||||
public AlbumListPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
|
||||
@@ -40,20 +42,20 @@ public class AlbumListPageViewModel extends AndroidViewModel {
|
||||
|
||||
switch (title) {
|
||||
case Constants.ALBUM_RECENTLY_PLAYED:
|
||||
albumRepository.getAlbums("recent", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
albumRepository.getAlbums("recent", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
break;
|
||||
case Constants.ALBUM_MOST_PLAYED:
|
||||
albumRepository.getAlbums("frequent", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
albumRepository.getAlbums("frequent", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
break;
|
||||
case Constants.ALBUM_RECENTLY_ADDED:
|
||||
albumRepository.getAlbums("newest", 500, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
albumRepository.getAlbums("newest", maxNumber, null, null).observe(owner, albums -> albumList.setValue(albums));
|
||||
break;
|
||||
case Constants.ALBUM_STARRED:
|
||||
albumList = albumRepository.getStarredAlbums(false, -1);
|
||||
break;
|
||||
case Constants.ALBUM_NEW_RELEASES:
|
||||
int currentYear = Calendar.getInstance().get(Calendar.YEAR);
|
||||
albumRepository.getAlbums("byYear", 500, currentYear, currentYear).observe(owner, albums -> {
|
||||
albumRepository.getAlbums("byYear", maxNumber, currentYear, currentYear).observe(owner, albums -> {
|
||||
albums.sort(Comparator.comparing(AlbumID3::getCreated).reversed());
|
||||
albumList.postValue(albums.subList(0, Math.min(20, albums.size())));
|
||||
});
|
||||
|
||||
@@ -4,7 +4,9 @@ import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
@@ -18,8 +20,8 @@ import java.util.List;
|
||||
public class AlbumPageViewModel extends AndroidViewModel {
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
|
||||
private AlbumID3 album;
|
||||
private String albumId;
|
||||
private final MutableLiveData<AlbumID3> album = new MutableLiveData<>(null);
|
||||
|
||||
public AlbumPageViewModel(@NonNull Application application) {
|
||||
super(application);
|
||||
@@ -29,22 +31,27 @@ public class AlbumPageViewModel extends AndroidViewModel {
|
||||
}
|
||||
|
||||
public LiveData<List<Child>> getAlbumSongLiveList() {
|
||||
return albumRepository.getAlbumTracks(album.getId());
|
||||
return albumRepository.getAlbumTracks(albumId);
|
||||
}
|
||||
|
||||
public AlbumID3 getAlbum() {
|
||||
public MutableLiveData<AlbumID3> getAlbum() {
|
||||
return album;
|
||||
}
|
||||
|
||||
public void setAlbum(AlbumID3 album) {
|
||||
this.album = album;
|
||||
public void setAlbum(LifecycleOwner owner, AlbumID3 album) {
|
||||
this.albumId = album.getId();
|
||||
this.album.postValue(album);
|
||||
|
||||
albumRepository.getAlbum(album.getId()).observe(owner, albums -> {
|
||||
if (albums != null) this.album.setValue(albums);
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<ArtistID3> getArtist() {
|
||||
return artistRepository.getArtistInfo(album.getArtistId());
|
||||
return artistRepository.getArtistInfo(albumId);
|
||||
}
|
||||
|
||||
public LiveData<AlbumInfo> getAlbumInfo() {
|
||||
return albumRepository.getAlbumInfo(album.getId());
|
||||
return albumRepository.getAlbumInfo(albumId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ public class HomeRearrangementViewModel extends AndroidViewModel {
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_MOST_PLAYED, getApplication().getString(R.string.home_title_most_played), true, 11));
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_LAST_PLAYED, getApplication().getString(R.string.home_title_last_played), true, 12));
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_RECENTLY_ADDED, getApplication().getString(R.string.home_title_recently_added), true, 13));
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_SHARED, getApplication().getString(R.string.home_title_shares), true, 14));
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_PINNED_PLAYLISTS, getApplication().getString(R.string.home_title_pinned_playlists), true, 14));
|
||||
sectors.add(new HomeSector(Constants.HOME_SECTOR_SHARED, getApplication().getString(R.string.home_title_shares), true, 15));
|
||||
|
||||
return sectors;
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@ 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.PlaylistRepository;
|
||||
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.Playlist;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.google.common.reflect.TypeToken;
|
||||
@@ -32,6 +34,7 @@ import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class HomeViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "HomeViewModel";
|
||||
@@ -41,6 +44,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
private final ArtistRepository artistRepository;
|
||||
private final ChronologyRepository chronologyRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
private final PlaylistRepository playlistRepository;
|
||||
private final SharingRepository sharingRepository;
|
||||
|
||||
private final MutableLiveData<List<Child>> dicoverSongSample = new MutableLiveData<>(null);
|
||||
@@ -60,6 +64,7 @@ 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<Playlist>> pinnedPlaylists = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<Share>> shares = new MutableLiveData<>(null);
|
||||
|
||||
private List<HomeSector> sectors;
|
||||
@@ -74,6 +79,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
artistRepository = new ArtistRepository();
|
||||
chronologyRepository = new ChronologyRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
playlistRepository = new PlaylistRepository();
|
||||
sharingRepository = new SharingRepository();
|
||||
|
||||
setOfflineFavorite();
|
||||
@@ -91,9 +97,17 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
return songRepository.getRandomSample(100, null, null);
|
||||
}
|
||||
|
||||
public LiveData<List<Chronology>> getGridSongSample(LifecycleOwner owner) {
|
||||
public LiveData<List<Chronology>> getChronologySample(LifecycleOwner owner) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
String server = Preferences.getServerId();
|
||||
chronologyRepository.getLastWeek(server).observe(owner, thisGridTopSong::postValue);
|
||||
|
||||
int currentWeek = cal.get(Calendar.WEEK_OF_YEAR);
|
||||
long start = cal.getTimeInMillis();
|
||||
|
||||
cal.set(Calendar.WEEK_OF_YEAR, currentWeek - 1);
|
||||
long end = cal.getTimeInMillis();
|
||||
|
||||
chronologyRepository.getChronology(server, start, end).observe(owner, thisGridTopSong::postValue);
|
||||
return thisGridTopSong;
|
||||
}
|
||||
|
||||
@@ -195,7 +209,7 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
mediaInstantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media, 20).observe(owner, mediaInstantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, mediaInstantMix::postValue);
|
||||
|
||||
return mediaInstantMix;
|
||||
}
|
||||
@@ -216,6 +230,24 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
return artistBestOf;
|
||||
}
|
||||
|
||||
public LiveData<List<Playlist>> getPinnedPlaylists(LifecycleOwner owner) {
|
||||
pinnedPlaylists.setValue(Collections.emptyList());
|
||||
|
||||
playlistRepository.getPlaylists(false, -1).observe(owner, remotes -> {
|
||||
playlistRepository.getPinnedPlaylists().observe(owner, locals -> {
|
||||
if (remotes != null && locals != null) {
|
||||
List<Playlist> toReturn = remotes.stream()
|
||||
.filter(remote -> locals.stream().anyMatch(local -> local.getId().equals(remote.getId())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
pinnedPlaylists.setValue(toReturn);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return pinnedPlaylists;
|
||||
}
|
||||
|
||||
public LiveData<List<Share>> getShares(LifecycleOwner owner) {
|
||||
if (shares.getValue() == null) {
|
||||
sharingRepository.getShares().observe(owner, shares::postValue);
|
||||
@@ -228,6 +260,31 @@ public class HomeViewModel extends AndroidViewModel {
|
||||
return songRepository.getStarredSongs(false, -1);
|
||||
}
|
||||
|
||||
public void changeChronologyPeriod(LifecycleOwner owner, int period) {
|
||||
Calendar cal = Calendar.getInstance();
|
||||
String server = Preferences.getServerId();
|
||||
int currentWeek = cal.get(Calendar.WEEK_OF_YEAR);
|
||||
|
||||
long start = 0;
|
||||
long end = 0;
|
||||
|
||||
if (period == 0) {
|
||||
start = cal.getTimeInMillis();
|
||||
cal.set(Calendar.WEEK_OF_YEAR, currentWeek - 1);
|
||||
end = cal.getTimeInMillis();
|
||||
} else if (period == 1) {
|
||||
start = cal.getTimeInMillis();
|
||||
cal.set(Calendar.WEEK_OF_YEAR, currentWeek - 4);
|
||||
end = cal.getTimeInMillis();
|
||||
} else if (period == 2) {
|
||||
start = cal.getTimeInMillis();
|
||||
cal.set(Calendar.WEEK_OF_YEAR, currentWeek - 52);
|
||||
end = cal.getTimeInMillis();
|
||||
}
|
||||
|
||||
chronologyRepository.getChronology(server, start, end).observe(owner, thisGridTopSong::postValue);
|
||||
}
|
||||
|
||||
public void refreshDiscoverySongSample(LifecycleOwner owner) {
|
||||
songRepository.getRandomSample(10, null, null).observe(owner, dicoverSongSample::postValue);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.github.models.LatestRelease;
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||
import com.cappielloantonio.tempo.repository.SystemRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.OpenSubsonicExtension;
|
||||
@@ -36,4 +37,8 @@ public class MainViewModel extends AndroidViewModel {
|
||||
public LiveData<List<OpenSubsonicExtension>> getOpenSubsonicExtensions() {
|
||||
return systemRepository.getOpenSubsonicExtensions();
|
||||
}
|
||||
|
||||
public LiveData<LatestRelease> checkTempoUpdate() {
|
||||
return systemRepository.checkTempoUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,13 @@ import androidx.media3.common.util.UnstableApi;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.repository.AlbumRepository;
|
||||
import com.cappielloantonio.tempo.repository.ArtistRepository;
|
||||
import com.cappielloantonio.tempo.repository.FavoriteRepository;
|
||||
import com.cappielloantonio.tempo.repository.OpenRepository;
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
|
||||
@@ -40,6 +42,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
private static final String TAG = "PlayerBottomSheetViewModel";
|
||||
|
||||
private final SongRepository songRepository;
|
||||
private final AlbumRepository albumRepository;
|
||||
private final ArtistRepository artistRepository;
|
||||
private final QueueRepository queueRepository;
|
||||
private final FavoriteRepository favoriteRepository;
|
||||
@@ -48,6 +51,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
private final MutableLiveData<LyricsList> lyricsListLiveData = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<String> descriptionLiveData = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<Child> liveMedia = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<AlbumID3> liveAlbum = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<ArtistID3> liveArtist = new MutableLiveData<>(null);
|
||||
private final MutableLiveData<List<Child>> instantMix = new MutableLiveData<>(null);
|
||||
private boolean lyricsSyncState = true;
|
||||
@@ -57,6 +61,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
super(application);
|
||||
|
||||
songRepository = new SongRepository();
|
||||
albumRepository = new AlbumRepository();
|
||||
artistRepository = new ArtistRepository();
|
||||
queueRepository = new QueueRepository();
|
||||
favoriteRepository = new FavoriteRepository();
|
||||
@@ -162,6 +167,23 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<AlbumID3> getLiveAlbum() {
|
||||
return liveAlbum;
|
||||
}
|
||||
|
||||
public void setLiveAlbum(LifecycleOwner owner, String mediaType, String AlbumId) {
|
||||
if (mediaType != null) {
|
||||
switch (mediaType) {
|
||||
case Constants.MEDIA_TYPE_MUSIC:
|
||||
albumRepository.getAlbum(AlbumId).observe(owner, liveAlbum::postValue);
|
||||
break;
|
||||
case Constants.MEDIA_TYPE_PODCAST:
|
||||
liveAlbum.postValue(null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public LiveData<ArtistID3> getLiveArtist() {
|
||||
return liveArtist;
|
||||
}
|
||||
@@ -190,7 +212,7 @@ public class PlayerBottomSheetViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getMediaInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media, 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,9 @@ import android.app.Application;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.AndroidViewModel;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.PlaylistRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
@@ -35,4 +37,22 @@ public class PlaylistPageViewModel extends AndroidViewModel {
|
||||
public void setPlaylist(Playlist playlist) {
|
||||
this.playlist = playlist;
|
||||
}
|
||||
|
||||
public LiveData<Boolean> isPinned(LifecycleOwner owner) {
|
||||
MutableLiveData<Boolean> isPinnedLive = new MutableLiveData<>();
|
||||
|
||||
playlistRepository.getPinnedPlaylists().observe(owner, playlists -> {
|
||||
isPinnedLive.postValue(playlists.stream().anyMatch(obj -> obj.getId().equals(playlist.getId())));
|
||||
});
|
||||
|
||||
return isPinnedLive;
|
||||
}
|
||||
|
||||
public void setPinned(boolean isNowPinned) {
|
||||
if (isNowPinned) {
|
||||
playlistRepository.insert(playlist);
|
||||
} else {
|
||||
playlistRepository.delete(playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ public class SongBottomSheetViewModel extends AndroidViewModel {
|
||||
public LiveData<List<Child>> getInstantMix(LifecycleOwner owner, Child media) {
|
||||
instantMix.setValue(Collections.emptyList());
|
||||
|
||||
songRepository.getInstantMix(media, 20).observe(owner, instantMix::postValue);
|
||||
songRepository.getInstantMix(media.getId(), 20).observe(owner, instantMix::postValue);
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user