Repo created
This commit is contained in:
parent
16b40d913d
commit
58cebe4c98
697 changed files with 71766 additions and 2 deletions
102
app/src/main/AndroidManifest.xml
Normal file
102
app/src/main/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
|
||||
<application
|
||||
android:name="App"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:localeConfig="@xml/locale_config"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme.SplashScreen"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
<!-- Declare that this session demo supports Android Auto. -->
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.car.application"
|
||||
android:resource="@xml/auto_app_desc" />
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||
android:value="androidx.media3.cast.DefaultCastOptionsProvider" />
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.TintableAttributionIcon"
|
||||
android:resource="@drawable/ic_graphic_eq" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.activity.MainActivity"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustPan|adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:host="asset"
|
||||
android:scheme="tempo" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".service.MediaService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="mediaPlayback">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.session.MediaLibraryService" />
|
||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.DownloaderService"
|
||||
android:exported="true"
|
||||
android:foregroundServiceType="dataSync">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
|
||||
android:enabled="false"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="autoStoreLocales"
|
||||
android:value="true" />
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".widget.WidgetProvider4x1"
|
||||
android:exported="false"
|
||||
android:label="@string/widget_label">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_info"/>
|
||||
</receiver>
|
||||
|
||||
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
109
app/src/main/java/com/cappielloantonio/tempo/App.java
Normal file
109
app/src/main/java/com/cappielloantonio/tempo/App.java
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
package com.cappielloantonio.tempo;
|
||||
|
||||
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;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
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
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
String themePref = sharedPreferences.getString(Preferences.THEME, ThemeHelper.DEFAULT_MODE);
|
||||
ThemeHelper.applyTheme(themePref);
|
||||
|
||||
instance = new App();
|
||||
context = getApplicationContext();
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
public static App getInstance() {
|
||||
if (instance == null) {
|
||||
instance = new App();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static Context getContext() {
|
||||
if (context == null) {
|
||||
context = getInstance();
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
public static Subsonic getSubsonicClientInstance(boolean override) {
|
||||
if (subsonic == null || override) {
|
||||
subsonic = getSubsonicClient();
|
||||
}
|
||||
return subsonic;
|
||||
}
|
||||
|
||||
public static Github getGithubClientInstance() {
|
||||
if (github == null) {
|
||||
github = new Github();
|
||||
}
|
||||
return github;
|
||||
}
|
||||
|
||||
public SharedPreferences getPreferences() {
|
||||
if (preferences == null) {
|
||||
preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
}
|
||||
|
||||
return preferences;
|
||||
}
|
||||
|
||||
public static void refreshSubsonicClient() {
|
||||
subsonic = getSubsonicClient();
|
||||
}
|
||||
|
||||
private static Subsonic getSubsonicClient() {
|
||||
SubsonicPreferences preferences = getSubsonicPreferences();
|
||||
|
||||
if (preferences.getAuthentication() != null) {
|
||||
if (preferences.getAuthentication().getPassword() != null)
|
||||
Preferences.setPassword(preferences.getAuthentication().getPassword());
|
||||
if (preferences.getAuthentication().getToken() != null)
|
||||
Preferences.setToken(preferences.getAuthentication().getToken());
|
||||
if (preferences.getAuthentication().getSalt() != null)
|
||||
Preferences.setSalt(preferences.getAuthentication().getSalt());
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.cappielloantonio.tempo.broadcast.receiver;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.ui.activity.MainActivity;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class ConnectivityStatusBroadcastReceiver extends BroadcastReceiver {
|
||||
private final MainActivity activity;
|
||||
|
||||
public ConnectivityStatusBroadcastReceiver(MainActivity activity) {
|
||||
this.activity = activity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
|
||||
boolean noConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
|
||||
|
||||
if (noConnectivity) {
|
||||
activity.bind.offlineModeTextView.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
activity.bind.offlineModeTextView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.cappielloantonio.tempo.database;
|
||||
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.room.AutoMigration;
|
||||
import androidx.room.Database;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.room.TypeConverters;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
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.LyricsDao;
|
||||
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;
|
||||
import com.cappielloantonio.tempo.database.dao.SessionMediaItemDao;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
import com.cappielloantonio.tempo.model.LyricsCache;
|
||||
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 = 12,
|
||||
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class, Favorite.class, SessionMediaItem.class, Playlist.class, LyricsCache.class},
|
||||
autoMigrations = {@AutoMigration(from = 10, to = 11), @AutoMigration(from = 11, to = 12)}
|
||||
)
|
||||
@TypeConverters({DateConverters.class})
|
||||
public abstract class AppDatabase extends RoomDatabase {
|
||||
private final static String DB_NAME = "tempo_db";
|
||||
private static AppDatabase instance;
|
||||
|
||||
public static synchronized AppDatabase getInstance() {
|
||||
if (instance == null) {
|
||||
instance = Room.databaseBuilder(App.getContext(), AppDatabase.class, DB_NAME)
|
||||
.fallbackToDestructiveMigration()
|
||||
.build();
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
public abstract QueueDao queueDao();
|
||||
|
||||
public abstract ServerDao serverDao();
|
||||
|
||||
public abstract RecentSearchDao recentSearchDao();
|
||||
|
||||
public abstract DownloadDao downloadDao();
|
||||
|
||||
public abstract ChronologyDao chronologyDao();
|
||||
|
||||
public abstract FavoriteDao favoriteDao();
|
||||
|
||||
public abstract SessionMediaItemDao sessionMediaItemDao();
|
||||
|
||||
public abstract PlaylistDao playlistDao();
|
||||
|
||||
public abstract LyricsDao lyricsDao();
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.cappielloantonio.tempo.database.converter
|
||||
|
||||
import androidx.room.TypeConverter
|
||||
import java.util.*
|
||||
|
||||
class DateConverters {
|
||||
@TypeConverter
|
||||
fun fromTimestamp(value: Long?): Date? {
|
||||
return value?.let { Date(it) }
|
||||
}
|
||||
|
||||
@TypeConverter
|
||||
fun dateToTimestamp(date: Date?): Long? {
|
||||
return date?.time
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface ChronologyDao {
|
||||
@Query("SELECT * FROM chronology WHERE server == :server GROUP BY id ORDER BY timestamp DESC LIMIT :count")
|
||||
LiveData<List<Chronology>> getLastPlayed(String server, int count);
|
||||
|
||||
@Query("SELECT * FROM chronology WHERE timestamp >= :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)
|
||||
void insert(Chronology chronologyObject);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface DownloadDao {
|
||||
@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 download_state = 1 ORDER BY artist, album, disc_number, track ASC")
|
||||
List<Download> getAllSync();
|
||||
|
||||
@Query("SELECT * FROM download WHERE id = :id")
|
||||
Download getOne(String id);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Download download);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insertAll(List<Download> downloads);
|
||||
|
||||
@Query("UPDATE download SET download_state = 1 WHERE id = :id")
|
||||
void update(String id);
|
||||
|
||||
@Query("DELETE FROM download WHERE id = :id")
|
||||
void delete(String id);
|
||||
|
||||
@Query("DELETE FROM download WHERE id IN (:ids)")
|
||||
void deleteByIds(List<String> ids);
|
||||
|
||||
@Query("DELETE FROM download")
|
||||
void deleteAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface FavoriteDao {
|
||||
@Query("SELECT * FROM favorite")
|
||||
List<Favorite> getAll();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insert(Favorite favorite);
|
||||
|
||||
@Delete
|
||||
void delete(Favorite favorite);
|
||||
|
||||
@Query("DELETE FROM favorite")
|
||||
void deleteAll();
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.LyricsCache;
|
||||
|
||||
@Dao
|
||||
public interface LyricsDao {
|
||||
@Query("SELECT * FROM lyrics_cache WHERE song_id = :songId")
|
||||
LyricsCache getOne(String songId);
|
||||
|
||||
@Query("SELECT * FROM lyrics_cache WHERE song_id = :songId")
|
||||
LiveData<LyricsCache> observeOne(String songId);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(LyricsCache lyricsCache);
|
||||
|
||||
@Query("DELETE FROM lyrics_cache WHERE song_id = :songId")
|
||||
void delete(String songId);
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.models.Playlist;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface PlaylistDao {
|
||||
// @Query("SELECT * FROM playlist WHERE server=:serverId")
|
||||
// LiveData<List<Playlist>> getAll(String serverId);
|
||||
|
||||
@Query("SELECT * FROM playlist")
|
||||
LiveData<List<Playlist>> getAll();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Playlist playlist);
|
||||
|
||||
@Delete
|
||||
void delete(Playlist playlist);
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface QueueDao {
|
||||
@Query("SELECT * FROM queue")
|
||||
LiveData<List<Queue>> getAll();
|
||||
|
||||
@Query("SELECT * FROM queue")
|
||||
List<Queue> getAllSimple();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Queue songQueueObject);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insertAll(List<Queue> songQueueObjects);
|
||||
|
||||
@Query("DELETE FROM queue WHERE queue.track_order=:position")
|
||||
void delete(int position);
|
||||
|
||||
@Query("DELETE FROM queue")
|
||||
void deleteAll();
|
||||
|
||||
@Query("SELECT COUNT(*) FROM queue")
|
||||
int count();
|
||||
|
||||
@Query("UPDATE queue SET last_play=:timestamp WHERE id=:id")
|
||||
void setLastPlay(String id, long timestamp);
|
||||
|
||||
@Query("UPDATE queue SET playing_changed=:timestamp WHERE id=:id")
|
||||
void setPlayingChanged(String id, long timestamp);
|
||||
|
||||
@Query("SELECT * FROM queue ORDER BY last_play DESC LIMIT 1")
|
||||
Queue getLastPlayed();
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface RecentSearchDao {
|
||||
@Query("SELECT * FROM recent_search ORDER BY search DESC")
|
||||
List<String> getRecent();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(RecentSearch search);
|
||||
|
||||
@Delete
|
||||
void delete(RecentSearch search);
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Delete;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface ServerDao {
|
||||
@Query("SELECT * FROM server")
|
||||
LiveData<List<Server>> getAll();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(Server server);
|
||||
|
||||
@Delete
|
||||
void delete(Server server);
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.cappielloantonio.tempo.database.dao;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.model.SessionMediaItem;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface SessionMediaItemDao {
|
||||
@Query("SELECT * FROM session_media_item WHERE id = :id")
|
||||
SessionMediaItem get(String id);
|
||||
|
||||
@Query("SELECT * FROM session_media_item WHERE timestamp = :timestamp")
|
||||
List<SessionMediaItem> get(long timestamp);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insert(SessionMediaItem sessionMediaItem);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insertAll(List<SessionMediaItem> sessionMediaItems);
|
||||
|
||||
@Query("DELETE FROM session_media_item")
|
||||
void deleteAll();
|
||||
}
|
||||
|
|
@ -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 = "eddyizm";
|
||||
private static final String REPO = "Tempus";
|
||||
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,32 @@
|
|||
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;
|
||||
String remoteTag = release.getTagName().replaceAll("^\\D+", "");
|
||||
|
||||
try {
|
||||
String[] local = BuildConfig.VERSION_NAME.split("\\.");
|
||||
String[] remote = remoteTag.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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.cappielloantonio.tempo.glide;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
import com.bumptech.glide.annotation.GlideModule;
|
||||
import com.bumptech.glide.load.DecodeFormat;
|
||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
|
||||
import com.bumptech.glide.Registry;
|
||||
import com.bumptech.glide.module.AppGlideModule;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
@GlideModule
|
||||
public class CustomGlideModule extends AppGlideModule {
|
||||
@Override
|
||||
public void applyOptions(@NonNull Context context, GlideBuilder builder) {
|
||||
int diskCacheSize = Preferences.getImageCacheSize() * 1024 * 1024;
|
||||
builder.setDiskCache(new InternalCacheDiskCacheFactory(context, "cache", diskCacheSize));
|
||||
builder.setDefaultRequestOptions(new RequestOptions().format(DecodeFormat.PREFER_RGB_565));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
|
||||
registry.replace(String.class, InputStream.class, new IPv6StringLoader.Factory());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package com.cappielloantonio.tempo.glide;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.RequestBuilder;
|
||||
import com.bumptech.glide.RequestManager;
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop;
|
||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
|
||||
import com.bumptech.glide.request.RequestOptions;
|
||||
import com.bumptech.glide.request.target.CustomTarget;
|
||||
import com.bumptech.glide.signature.ObjectKey;
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.Util;
|
||||
import com.google.android.material.elevation.SurfaceColors;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class CustomGlideRequest {
|
||||
private static final String TAG = "CustomGlideRequest";
|
||||
|
||||
public static final int CORNER_RADIUS = Preferences.isCornerRoundingEnabled() ? Preferences.getRoundedCornerSize() : 1;
|
||||
|
||||
public static final DiskCacheStrategy DEFAULT_DISK_CACHE_STRATEGY = DiskCacheStrategy.ALL;
|
||||
|
||||
public enum ResourceType {
|
||||
Unknown,
|
||||
Album,
|
||||
Artist,
|
||||
Folder,
|
||||
Directory,
|
||||
Playlist,
|
||||
Podcast,
|
||||
Radio,
|
||||
Song,
|
||||
}
|
||||
|
||||
public static RequestOptions createRequestOptions(Context context, String item, ResourceType type) {
|
||||
return new RequestOptions()
|
||||
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
|
||||
.fallback(getPlaceholder(context, type))
|
||||
.error(getPlaceholder(context, type))
|
||||
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
|
||||
.signature(new ObjectKey(item != null ? item : 0))
|
||||
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Drawable getPlaceholder(Context context, ResourceType type) {
|
||||
switch (type) {
|
||||
case Album:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_album);
|
||||
case Artist:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_artist);
|
||||
case Folder:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_folder);
|
||||
case Directory:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_directory);
|
||||
case Playlist:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_playlist);
|
||||
case Podcast:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_podcast);
|
||||
case Radio:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_radio);
|
||||
case Song:
|
||||
return AppCompatResources.getDrawable(context, R.drawable.ic_placeholder_song);
|
||||
default:
|
||||
case Unknown:
|
||||
return new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context));
|
||||
}
|
||||
}
|
||||
|
||||
public static String createUrl(String item, int size) {
|
||||
Map<String, String> params = App.getSubsonicClientInstance(false).getParams();
|
||||
|
||||
StringBuilder uri = new StringBuilder();
|
||||
|
||||
uri.append(App.getSubsonicClientInstance(false).getUrl());
|
||||
uri.append("getCoverArt");
|
||||
|
||||
if (params.containsKey("u") && params.get("u") != null)
|
||||
uri.append("?u=").append(Util.encode(params.get("u")));
|
||||
if (params.containsKey("p") && params.get("p") != null)
|
||||
uri.append("&p=").append(params.get("p"));
|
||||
if (params.containsKey("s") && params.get("s") != null)
|
||||
uri.append("&s=").append(params.get("s"));
|
||||
if (params.containsKey("t") && params.get("t") != null)
|
||||
uri.append("&t=").append(params.get("t"));
|
||||
if (params.containsKey("v") && params.get("v") != null)
|
||||
uri.append("&v=").append(params.get("v"));
|
||||
if (params.containsKey("c") && params.get("c") != null)
|
||||
uri.append("&c=").append(params.get("c"));
|
||||
if (size != -1)
|
||||
uri.append("&size=").append(size);
|
||||
|
||||
uri.append("&id=").append(item);
|
||||
|
||||
Log.d(TAG, "createUrl() " + uri);
|
||||
|
||||
return uri.toString();
|
||||
}
|
||||
|
||||
public static void loadAlbumArtBitmap(Context context,
|
||||
String coverId,
|
||||
int size,
|
||||
CustomTarget<Bitmap> target) {
|
||||
String url = createUrl(coverId, size);
|
||||
Glide.with(context)
|
||||
.asBitmap()
|
||||
.load(url)
|
||||
.apply(createRequestOptions(context, coverId, ResourceType.Album))
|
||||
.into(target);
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private final RequestManager requestManager;
|
||||
private String item;
|
||||
|
||||
private Builder(Context context, String item, ResourceType type) {
|
||||
this.requestManager = Glide.with(context);
|
||||
|
||||
if (item != null && !Preferences.isDataSavingMode()) {
|
||||
this.item = createUrl(item, Preferences.getImageSize());
|
||||
}
|
||||
|
||||
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item, type));
|
||||
}
|
||||
|
||||
public static Builder from(Context context, String item, ResourceType type) {
|
||||
return new Builder(context, item, type);
|
||||
}
|
||||
|
||||
public RequestBuilder<Drawable> build() {
|
||||
return requestManager
|
||||
.load(item)
|
||||
.transition(DrawableTransitionOptions.withCrossFade());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
package com.cappielloantonio.tempo.glide;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.DataSource;
|
||||
import com.bumptech.glide.load.Options;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.ModelLoader;
|
||||
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
||||
import com.bumptech.glide.signature.ObjectKey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class IPv6StringLoader implements ModelLoader<String, InputStream> {
|
||||
private static final int DEFAULT_TIMEOUT_MS = 2500;
|
||||
|
||||
@Override
|
||||
public boolean handles(@NonNull String model) {
|
||||
return model.startsWith("http://") || model.startsWith("https://");
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoadData<InputStream> buildLoadData(@NonNull String model, int width, int height, @NonNull Options options) {
|
||||
if (!handles(model)) {
|
||||
return null;
|
||||
}
|
||||
return new LoadData<>(new ObjectKey(model), new IPv6StreamFetcher(model));
|
||||
}
|
||||
|
||||
private static class IPv6StreamFetcher implements DataFetcher<InputStream> {
|
||||
private final String model;
|
||||
private InputStream stream;
|
||||
private HttpURLConnection connection;
|
||||
|
||||
IPv6StreamFetcher(String model) {
|
||||
this.model = model;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
|
||||
try {
|
||||
URL url = new URL(model);
|
||||
connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setConnectTimeout(DEFAULT_TIMEOUT_MS);
|
||||
connection.setReadTimeout(DEFAULT_TIMEOUT_MS);
|
||||
connection.setUseCaches(true);
|
||||
connection.setDoInput(true);
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() / 100 != 2) {
|
||||
callback.onLoadFailed(new IOException("Request failed with status code: " + connection.getResponseCode()));
|
||||
return;
|
||||
}
|
||||
|
||||
stream = connection.getInputStream();
|
||||
callback.onDataReady(stream);
|
||||
} catch (IOException e) {
|
||||
callback.onLoadFailed(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
if (connection != null) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
// HttpURLConnection does not provide a direct cancel mechanism.
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Class<InputStream> getDataClass() {
|
||||
return InputStream.class;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DataSource getDataSource() {
|
||||
return DataSource.REMOTE;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements ModelLoaderFactory<String, InputStream> {
|
||||
@NonNull
|
||||
@Override
|
||||
public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
|
||||
return new IPv6StringLoader();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void teardown() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.cappielloantonio.tempo.helper;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
|
||||
public class ThemeHelper {
|
||||
private static final String TAG = "ThemeHelper";
|
||||
|
||||
public static final String LIGHT_MODE = "light";
|
||||
public static final String DARK_MODE = "dark";
|
||||
public static final String DEFAULT_MODE = "default";
|
||||
|
||||
public static void applyTheme(@NonNull String themePref) {
|
||||
switch (themePref) {
|
||||
case LIGHT_MODE: {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO);
|
||||
break;
|
||||
}
|
||||
case DARK_MODE: {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
||||
} else {
|
||||
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearSnapHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class CustomLinearSnapHelper extends LinearSnapHelper {
|
||||
@Override
|
||||
public View findSnapView(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
if (!needToDoSnap(linearLayoutManager)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return super.findSnapView(layoutManager);
|
||||
}
|
||||
|
||||
public boolean needToDoSnap(LinearLayoutManager linearLayoutManager) {
|
||||
return linearLayoutManager.findFirstCompletelyVisibleItemPosition() != 0 && linearLayoutManager.findLastCompletelyVisibleItemPosition() != linearLayoutManager.getItemCount() - 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DotsIndicatorDecoration extends RecyclerView.ItemDecoration {
|
||||
private static final String TAG = "DotsIndicatorDecoration";
|
||||
|
||||
private final int indicatorHeight;
|
||||
private final int indicatorItemPadding;
|
||||
private final int radius;
|
||||
|
||||
private final Paint inactivePaint = new Paint();
|
||||
private final Paint activePaint = new Paint();
|
||||
|
||||
public DotsIndicatorDecoration(int radius, int padding, int indicatorHeight, @ColorInt int colorInactive, @ColorInt int colorActive) {
|
||||
float strokeWidth = Resources.getSystem().getDisplayMetrics().density * 1;
|
||||
this.radius = radius;
|
||||
|
||||
inactivePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
inactivePaint.setStrokeWidth(strokeWidth);
|
||||
inactivePaint.setStyle(Paint.Style.STROKE);
|
||||
inactivePaint.setAntiAlias(true);
|
||||
inactivePaint.setColor(colorInactive);
|
||||
|
||||
activePaint.setStrokeCap(Paint.Cap.ROUND);
|
||||
activePaint.setStrokeWidth(strokeWidth);
|
||||
activePaint.setStyle(Paint.Style.FILL);
|
||||
activePaint.setAntiAlias(true);
|
||||
activePaint.setColor(colorActive);
|
||||
|
||||
this.indicatorItemPadding = padding;
|
||||
this.indicatorHeight = indicatorHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(@NotNull Canvas c, @NotNull RecyclerView parent, @NotNull RecyclerView.State state) {
|
||||
super.onDrawOver(c, parent, state);
|
||||
|
||||
if (parent.getAdapter() == null) return;
|
||||
|
||||
int itemCount = (int) Math.ceil((double) parent.getAdapter().getItemCount() / 5);
|
||||
|
||||
if (itemCount <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// center horizontally, calculate width and subtract half from center
|
||||
float totalLength = this.radius * 2 * itemCount;
|
||||
float paddingBetweenItems = Math.max(0, itemCount - 1) * indicatorItemPadding;
|
||||
float indicatorTotalWidth = totalLength + paddingBetweenItems;
|
||||
float indicatorStartX = (parent.getWidth() - indicatorTotalWidth) / 2f;
|
||||
|
||||
// center vertically in the allotted space
|
||||
float indicatorPosY = parent.getHeight() - indicatorHeight - (float) indicatorItemPadding / 4;
|
||||
|
||||
drawInactiveDots(c, indicatorStartX, indicatorPosY, itemCount);
|
||||
|
||||
final int activePosition;
|
||||
|
||||
if (parent.getLayoutManager() instanceof GridLayoutManager) {
|
||||
activePosition = ((GridLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
} else if (parent.getLayoutManager() instanceof LinearLayoutManager) {
|
||||
activePosition = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
|
||||
} else {
|
||||
// not supported layout manager
|
||||
return;
|
||||
}
|
||||
|
||||
if (activePosition == RecyclerView.NO_POSITION) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find offset of active page if the user is scrolling
|
||||
final View activeChild = parent.getLayoutManager().findViewByPosition(activePosition);
|
||||
if (activeChild == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawActiveDot(c, indicatorStartX, indicatorPosY, activePosition);
|
||||
}
|
||||
|
||||
private void drawInactiveDots(Canvas c, float indicatorStartX, float indicatorPosY, int itemCount) {
|
||||
// width of item indicator including padding
|
||||
final float itemWidth = this.radius * 2 + indicatorItemPadding;
|
||||
|
||||
float start = indicatorStartX + radius;
|
||||
for (int i = 0; i < itemCount; i++) {
|
||||
c.drawCircle(start, indicatorPosY, radius, inactivePaint);
|
||||
start += itemWidth;
|
||||
}
|
||||
}
|
||||
|
||||
private void drawActiveDot(Canvas c, float indicatorStartX, float indicatorPosY, int highlightPosition) {
|
||||
// width of item indicator including padding
|
||||
final float itemWidth = this.radius * 2 + indicatorItemPadding;
|
||||
float highlightStart = (float) Math.ceil(indicatorStartX + radius + itemWidth * highlightPosition / 5);
|
||||
c.drawCircle(highlightStart, indicatorPosY, radius, activePaint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NotNull Rect outRect, @NotNull View view, @NotNull RecyclerView parent, @NotNull RecyclerView.State state) {
|
||||
super.getItemOffsets(outRect, view, parent, state);
|
||||
outRect.bottom = indicatorHeight;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class FastScrollbar extends LinearLayout {
|
||||
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||
private static final int TRACK_SNAP_RANGE = 5;
|
||||
|
||||
private TextView bubble;
|
||||
private View handle;
|
||||
private RecyclerView recyclerView;
|
||||
private int height;
|
||||
private boolean isInitialized = false;
|
||||
private ObjectAnimator currentAnimator = null;
|
||||
|
||||
private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) {
|
||||
updateBubbleAndHandlePosition();
|
||||
}
|
||||
};
|
||||
|
||||
public interface BubbleTextGetter {
|
||||
String getTextToShowInBubble(int pos);
|
||||
}
|
||||
|
||||
public FastScrollbar(final Context context, final AttributeSet attrs, final int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public FastScrollbar(final Context context) {
|
||||
super(context);
|
||||
init(context);
|
||||
}
|
||||
|
||||
public FastScrollbar(final Context context, final AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context);
|
||||
}
|
||||
|
||||
protected void init(Context context) {
|
||||
if (isInitialized) return;
|
||||
isInitialized = true;
|
||||
setOrientation(HORIZONTAL);
|
||||
setClipChildren(false);
|
||||
}
|
||||
|
||||
public void setViewsToUse(@LayoutRes int layoutResId, @IdRes int bubbleResId, @IdRes int handleResId) {
|
||||
final LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||
inflater.inflate(layoutResId, this, true);
|
||||
bubble = findViewById(bubbleResId);
|
||||
if (bubble != null) bubble.setVisibility(INVISIBLE);
|
||||
handle = findViewById(handleResId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
height = h;
|
||||
updateBubbleAndHandlePosition();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(@NonNull MotionEvent event) {
|
||||
final int action = event.getAction();
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
if (event.getX() < handle.getX() - ViewCompat.getPaddingStart(handle)) return false;
|
||||
if (currentAnimator != null) currentAnimator.cancel();
|
||||
if (bubble != null && bubble.getVisibility() == INVISIBLE) showBubble();
|
||||
handle.setSelected(true);
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
final float y = event.getY();
|
||||
setBubbleAndHandlePosition(y);
|
||||
setRecyclerViewPosition(y);
|
||||
return true;
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
handle.setSelected(false);
|
||||
hideBubble();
|
||||
return true;
|
||||
}
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public void setRecyclerView(final RecyclerView recyclerView) {
|
||||
if (this.recyclerView != recyclerView) {
|
||||
if (this.recyclerView != null)
|
||||
this.recyclerView.removeOnScrollListener(onScrollListener);
|
||||
this.recyclerView = recyclerView;
|
||||
if (this.recyclerView == null) return;
|
||||
recyclerView.addOnScrollListener(onScrollListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
if (recyclerView != null) {
|
||||
recyclerView.removeOnScrollListener(onScrollListener);
|
||||
recyclerView = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void setRecyclerViewPosition(float y) {
|
||||
if (recyclerView != null) {
|
||||
final int itemCount = recyclerView.getAdapter().getItemCount();
|
||||
float proportion;
|
||||
if (handle.getY() == 0) proportion = 0f;
|
||||
else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE)
|
||||
proportion = 1f;
|
||||
else proportion = y / (float) height;
|
||||
final int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount));
|
||||
((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0);
|
||||
final String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos);
|
||||
if (bubble != null) {
|
||||
bubble.setText(bubbleText);
|
||||
if (TextUtils.isEmpty(bubbleText)) {
|
||||
hideBubble();
|
||||
} else if (bubble.getVisibility() == View.INVISIBLE) {
|
||||
showBubble();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int getValueInRange(int min, int max, int value) {
|
||||
int minimum = Math.max(min, value);
|
||||
return Math.min(minimum, max);
|
||||
}
|
||||
|
||||
private void updateBubbleAndHandlePosition() {
|
||||
if (bubble == null || handle.isSelected()) return;
|
||||
|
||||
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset();
|
||||
final int verticalScrollRange = recyclerView.computeVerticalScrollRange();
|
||||
float proportion = (float) verticalScrollOffset / ((float) verticalScrollRange - height);
|
||||
setBubbleAndHandlePosition(height * proportion);
|
||||
}
|
||||
|
||||
private void setBubbleAndHandlePosition(float y) {
|
||||
final int handleHeight = handle.getHeight();
|
||||
handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2)));
|
||||
if (bubble != null) {
|
||||
int bubbleHeight = bubble.getHeight();
|
||||
bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight)));
|
||||
}
|
||||
}
|
||||
|
||||
private void showBubble() {
|
||||
if (bubble == null) return;
|
||||
bubble.setVisibility(VISIBLE);
|
||||
if (currentAnimator != null) currentAnimator.cancel();
|
||||
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||
currentAnimator.start();
|
||||
}
|
||||
|
||||
private void hideBubble() {
|
||||
if (bubble == null) return;
|
||||
if (currentAnimator != null) currentAnimator.cancel();
|
||||
currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION);
|
||||
currentAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
bubble.setVisibility(INVISIBLE);
|
||||
currentAnimator = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
super.onAnimationCancel(animation);
|
||||
bubble.setVisibility(INVISIBLE);
|
||||
currentAnimator = null;
|
||||
}
|
||||
});
|
||||
currentAnimator.start();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class GridItemDecoration extends RecyclerView.ItemDecoration {
|
||||
private final int spanCount;
|
||||
private final int spacing;
|
||||
private final boolean includeEdge;
|
||||
|
||||
public GridItemDecoration(int spanCount, int spacing, boolean includeEdge) {
|
||||
this.spanCount = spanCount;
|
||||
this.spacing = spacing;
|
||||
this.includeEdge = includeEdge;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, RecyclerView parent, @NonNull RecyclerView.State state) {
|
||||
int position = parent.getChildAdapterPosition(view); // item position
|
||||
int column = position % spanCount; // item column
|
||||
|
||||
if (includeEdge) {
|
||||
outRect.left = spacing - column * spacing / spanCount; // spacing - column * ((1f / spanCount) * spacing)
|
||||
outRect.right = (column + 1) * spacing / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
|
||||
|
||||
if (position < spanCount) { // top edge
|
||||
outRect.top = spacing;
|
||||
}
|
||||
outRect.bottom = spacing; // item bottom
|
||||
} else {
|
||||
outRect.left = column * spacing / spanCount; // column * ((1f / spanCount) * spacing)
|
||||
outRect.right = spacing - (column + 1) * spacing / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
|
||||
if (position >= spanCount) {
|
||||
outRect.top = spacing; // item top
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewConfiguration
|
||||
import android.widget.FrameLayout
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.sign
|
||||
|
||||
class NestedScrollableHost : FrameLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
|
||||
private var touchSlop = 0
|
||||
private var initialX = 0f
|
||||
private var initialY = 0f
|
||||
private val parentViewPager: ViewPager2?
|
||||
get() {
|
||||
var v: View? = parent as? View
|
||||
while (v != null && v !is ViewPager2) {
|
||||
v = v.parent as? View
|
||||
}
|
||||
return v as? ViewPager2
|
||||
}
|
||||
|
||||
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
|
||||
|
||||
init {
|
||||
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
||||
}
|
||||
|
||||
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
|
||||
val direction = -delta.sign.toInt()
|
||||
return when (orientation) {
|
||||
0 -> child?.canScrollHorizontally(direction) ?: false
|
||||
1 -> child?.canScrollVertically(direction) ?: false
|
||||
else -> throw IllegalArgumentException()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
|
||||
handleInterceptTouchEvent(e)
|
||||
return super.onInterceptTouchEvent(e)
|
||||
}
|
||||
|
||||
private fun handleInterceptTouchEvent(e: MotionEvent) {
|
||||
val orientation = parentViewPager?.orientation ?: return
|
||||
|
||||
// Early return if child can't scroll in same direction as parent
|
||||
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (e.action == MotionEvent.ACTION_DOWN) {
|
||||
initialX = e.x
|
||||
initialY = e.y
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
} else if (e.action == MotionEvent.ACTION_MOVE) {
|
||||
val dx = e.x - initialX
|
||||
val dy = e.y - initialY
|
||||
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
|
||||
|
||||
// assuming ViewPager2 touch-slop is 2x touch-slop of child
|
||||
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
|
||||
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
|
||||
|
||||
if (scaledDx > touchSlop || scaledDy > touchSlop) {
|
||||
if (isVpHorizontal == (scaledDy > scaledDx)) {
|
||||
// Gesture is perpendicular, allow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(false)
|
||||
} else {
|
||||
// Gesture is parallel, query child if movement in that direction is possible
|
||||
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
|
||||
// Child can scroll, disallow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(true)
|
||||
} else {
|
||||
// Child cannot scroll, allow all parents to intercept
|
||||
parent.requestDisallowInterceptTouchEvent(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener {
|
||||
private final LinearLayoutManager layoutManager;
|
||||
|
||||
protected PaginationScrollListener(LinearLayoutManager layoutManager) {
|
||||
this.layoutManager = layoutManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
|
||||
int visibleItemCount = layoutManager.getChildCount();
|
||||
int totalItemCount = layoutManager.getItemCount();
|
||||
int firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition();
|
||||
|
||||
if (!isLoading()) {
|
||||
if (firstVisibleItemPosition >= 0 && (visibleItemCount + firstVisibleItemPosition) >= (totalItemCount / 4 * 3)) {
|
||||
loadMoreItems();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void loadMoreItems();
|
||||
|
||||
public abstract boolean isLoading();
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.cappielloantonio.tempo.helper.recyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
public class SquareLayout extends RelativeLayout {
|
||||
public SquareLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public SquareLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public SquareLayout(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public SquareLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface ClickCallback {
|
||||
default void onMediaClick(Bundle bundle) {}
|
||||
default void onMediaLongClick(Bundle bundle) {}
|
||||
default void onAlbumClick(Bundle bundle) {}
|
||||
default void onAlbumLongClick(Bundle bundle) {}
|
||||
default void onArtistClick(Bundle bundle) {}
|
||||
default void onArtistLongClick(Bundle bundle) {}
|
||||
default void onGenreClick(Bundle bundle) {}
|
||||
default void onPlaylistClick(Bundle bundle) {}
|
||||
default void onPlaylistLongClick(Bundle bundle) {}
|
||||
default void onYearClick(Bundle bundle) {}
|
||||
default void onServerClick(Bundle bundle) {}
|
||||
default void onServerLongClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeAltClick(Bundle bundle) {}
|
||||
default void onPodcastEpisodeLongClick(Bundle bundle) {}
|
||||
default void onPodcastChannelClick(Bundle bundle) {}
|
||||
default void onPodcastChannelLongClick(Bundle bundle) {}
|
||||
default void onInternetRadioStationClick(Bundle bundle) {}
|
||||
default void onInternetRadioStationLongClick(Bundle bundle) {}
|
||||
default void onMusicFolderClick(Bundle bundle) {}
|
||||
default void onMusicDirectoryClick(Bundle bundle) {}
|
||||
default void onMusicIndexClick(Bundle bundle) {}
|
||||
default void onDownloadGroupLongClick(Bundle bundle) {}
|
||||
default void onShareClick(Bundle bundle) {}
|
||||
default void onShareLongClick(Bundle bundle) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface DecadesCallback {
|
||||
default void onLoadYear(int year) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface DialogClickCallback {
|
||||
default void onPositiveClick() {}
|
||||
|
||||
default void onNegativeClick() {}
|
||||
|
||||
default void onNeutralClick() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Keep
|
||||
public interface MediaCallback {
|
||||
default void onError(Exception exception) {}
|
||||
default void onLoadMedia(List<?> media) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface MediaIndexCallback {
|
||||
default void onRecovery(int index) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface PlaylistCallback {
|
||||
default void onDismiss() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
|
||||
public interface PodcastCallback {
|
||||
default void onDismiss() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
|
||||
public interface RadioCallback {
|
||||
default void onDismiss() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface ScanCallback {
|
||||
default void onError(Exception exception) {}
|
||||
default void onSuccess(boolean isScanning, long count) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface StarCallback {
|
||||
default void onError() {}
|
||||
default void onSuccess() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.interfaces;
|
||||
|
||||
import androidx.annotation.Keep;
|
||||
|
||||
@Keep
|
||||
public interface SystemCallback {
|
||||
default void onError(Exception exception) {}
|
||||
default void onSuccess(String password, String token, String salt) {}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.util.Preferences
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.Date
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "chronology")
|
||||
class Chronology(
|
||||
@PrimaryKey override val id: String,
|
||||
@ColumnInfo(name = "timestamp")
|
||||
var timestamp: Long = System.currentTimeMillis(),
|
||||
@ColumnInfo(name = "server")
|
||||
var server: String? = null,
|
||||
) : Child(id) {
|
||||
constructor(mediaItem: MediaItem) : this(mediaItem.mediaMetadata.extras!!.getString("id")!!) {
|
||||
parentId = mediaItem.mediaMetadata.extras!!.getString("parentId")
|
||||
isDir = mediaItem.mediaMetadata.extras!!.getBoolean("isDir")
|
||||
title = mediaItem.mediaMetadata.extras!!.getString("title")
|
||||
album = mediaItem.mediaMetadata.extras!!.getString("album")
|
||||
artist = mediaItem.mediaMetadata.extras!!.getString("artist")
|
||||
track = mediaItem.mediaMetadata.extras!!.getInt("track")
|
||||
year = mediaItem.mediaMetadata.extras!!.getInt("year")
|
||||
genre = mediaItem.mediaMetadata.extras!!.getString("genre")
|
||||
coverArtId = mediaItem.mediaMetadata.extras!!.getString("coverArtId")
|
||||
size = mediaItem.mediaMetadata.extras!!.getLong("size")
|
||||
contentType = mediaItem.mediaMetadata.extras!!.getString("contentType")
|
||||
suffix = mediaItem.mediaMetadata.extras!!.getString("suffix")
|
||||
transcodedContentType = mediaItem.mediaMetadata.extras!!.getString("transcodedContentType")
|
||||
transcodedSuffix = mediaItem.mediaMetadata.extras!!.getString("transcodedSuffix")
|
||||
duration = mediaItem.mediaMetadata.extras!!.getInt("duration")
|
||||
bitrate = mediaItem.mediaMetadata.extras!!.getInt("bitrate")
|
||||
samplingRate = mediaItem.mediaMetadata.extras!!.getInt("samplingRate")
|
||||
bitDepth = mediaItem.mediaMetadata.extras!!.getInt("bitDepth")
|
||||
path = mediaItem.mediaMetadata.extras!!.getString("path")
|
||||
isVideo = mediaItem.mediaMetadata.extras!!.getBoolean("isVideo")
|
||||
userRating = mediaItem.mediaMetadata.extras!!.getInt("userRating")
|
||||
averageRating = mediaItem.mediaMetadata.extras!!.getDouble("averageRating")
|
||||
playCount = mediaItem.mediaMetadata.extras!!.getLong("playCount")
|
||||
discNumber = mediaItem.mediaMetadata.extras!!.getInt("discNumber")
|
||||
created = Date(mediaItem.mediaMetadata.extras!!.getLong("created"))
|
||||
starred = Date(mediaItem.mediaMetadata.extras!!.getLong("starred"))
|
||||
albumId = mediaItem.mediaMetadata.extras!!.getString("albumId")
|
||||
artistId = mediaItem.mediaMetadata.extras!!.getString("artistId")
|
||||
type = mediaItem.mediaMetadata.extras!!.getString("type")
|
||||
bookmarkPosition = mediaItem.mediaMetadata.extras!!.getLong("bookmarkPosition")
|
||||
originalWidth = mediaItem.mediaMetadata.extras!!.getInt("originalWidth")
|
||||
originalHeight = mediaItem.mediaMetadata.extras!!.getInt("originalHeight")
|
||||
server = Preferences.getServerId()
|
||||
timestamp = Date().time
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "download")
|
||||
class Download(
|
||||
@PrimaryKey override val id: String,
|
||||
@ColumnInfo(name = "playlist_id")
|
||||
var playlistId: String? = null,
|
||||
@ColumnInfo(name = "playlist_name")
|
||||
var playlistName: String? = null,
|
||||
@ColumnInfo(name = "download_state", defaultValue = "1")
|
||||
var downloadState: Int = 0,
|
||||
@ColumnInfo(name = "download_uri", defaultValue = "")
|
||||
var downloadUri: String? = null,
|
||||
) : Child(id) {
|
||||
constructor(child: Child) : this(child.id) {
|
||||
parentId = child.parentId
|
||||
isDir = child.isDir
|
||||
title = child.title
|
||||
album = child.album
|
||||
artist = child.artist
|
||||
track = child.track
|
||||
year = child.year
|
||||
genre = child.genre
|
||||
coverArtId = child.coverArtId
|
||||
size = child.size
|
||||
contentType = child.contentType
|
||||
suffix = child.suffix
|
||||
transcodedContentType = child.transcodedContentType
|
||||
transcodedSuffix = child.transcodedSuffix
|
||||
duration = child.duration
|
||||
bitrate = child.bitrate
|
||||
samplingRate = child.samplingRate
|
||||
bitDepth = child.bitDepth
|
||||
path = child.path
|
||||
isVideo = child.isVideo
|
||||
userRating = child.userRating
|
||||
averageRating = child.averageRating
|
||||
playCount = child.playCount
|
||||
discNumber = child.discNumber
|
||||
created = child.created
|
||||
starred = child.starred
|
||||
albumId = child.albumId
|
||||
artistId = child.artistId
|
||||
type = child.type
|
||||
bookmarkPosition = child.bookmarkPosition
|
||||
originalWidth = child.originalWidth
|
||||
originalHeight = child.originalHeight
|
||||
}
|
||||
}
|
||||
|
||||
@Keep
|
||||
data class DownloadStack(
|
||||
var id: String,
|
||||
var view: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
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
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "favorite")
|
||||
data class Favorite(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "timestamp")
|
||||
var timestamp: Long,
|
||||
|
||||
@ColumnInfo(name = "songId")
|
||||
val songId: String?,
|
||||
|
||||
@ColumnInfo(name = "albumId")
|
||||
val albumId: String?,
|
||||
|
||||
@ColumnInfo(name = "artistId")
|
||||
val artistId: String?,
|
||||
|
||||
@ColumnInfo(name = "toStar")
|
||||
val toStar: Boolean,
|
||||
) : Parcelable {
|
||||
override fun toString(): String = (songId ?: "null") + (albumId ?: "null") + (artistId ?: "null")
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class HomeSector(
|
||||
val id: String,
|
||||
val sectorTitle: String,
|
||||
var isVisible: Boolean,
|
||||
val order: Int,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlin.jvm.JvmOverloads
|
||||
|
||||
@Keep
|
||||
@Entity(tableName = "lyrics_cache")
|
||||
data class LyricsCache @JvmOverloads constructor(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "song_id")
|
||||
var songId: String,
|
||||
@ColumnInfo(name = "artist")
|
||||
var artist: String? = null,
|
||||
@ColumnInfo(name = "title")
|
||||
var title: String? = null,
|
||||
@ColumnInfo(name = "lyrics")
|
||||
var lyrics: String? = null,
|
||||
@ColumnInfo(name = "structured_lyrics")
|
||||
var structuredLyrics: String? = null,
|
||||
@ColumnInfo(name = "updated_at")
|
||||
var updatedAt: Long = System.currentTimeMillis()
|
||||
)
|
||||
59
app/src/main/java/com/cappielloantonio/tempo/model/Queue.kt
Normal file
59
app/src/main/java/com/cappielloantonio/tempo/model/Queue.kt
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "queue")
|
||||
class Queue(
|
||||
override val id: String,
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "track_order")
|
||||
var trackOrder: Int = 0,
|
||||
@ColumnInfo(name = "last_play")
|
||||
var lastPlay: Long = 0,
|
||||
@ColumnInfo(name = "playing_changed")
|
||||
var playingChanged: Long = 0,
|
||||
@ColumnInfo(name = "stream_id")
|
||||
var streamId: String? = null,
|
||||
) : Child(id) {
|
||||
constructor(child: Child) : this(child.id) {
|
||||
parentId = child.parentId
|
||||
isDir = child.isDir
|
||||
title = child.title
|
||||
album = child.album
|
||||
artist = child.artist
|
||||
track = child.track
|
||||
year = child.year
|
||||
genre = child.genre
|
||||
coverArtId = child.coverArtId
|
||||
size = child.size
|
||||
contentType = child.contentType
|
||||
suffix = child.suffix
|
||||
transcodedContentType = child.transcodedContentType
|
||||
transcodedSuffix = child.transcodedSuffix
|
||||
duration = child.duration
|
||||
bitrate = child.bitrate
|
||||
samplingRate = child.samplingRate
|
||||
bitDepth = child.bitDepth
|
||||
path = child.path
|
||||
isVideo = child.isVideo
|
||||
userRating = child.userRating
|
||||
averageRating = child.averageRating
|
||||
playCount = child.playCount
|
||||
discNumber = child.discNumber
|
||||
created = child.created
|
||||
starred = child.starred
|
||||
albumId = child.albumId
|
||||
artistId = child.artistId
|
||||
type = child.type
|
||||
bookmarkPosition = child.bookmarkPosition
|
||||
originalWidth = child.originalWidth
|
||||
originalHeight = child.originalHeight
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.os.Parcelable
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "recent_search")
|
||||
data class RecentSearch(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "search")
|
||||
var search: String
|
||||
) : Parcelable
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
data class ReplayGain(
|
||||
var trackGain: Float = 0f,
|
||||
var albumGain: Float = 0f,
|
||||
)
|
||||
39
app/src/main/java/com/cappielloantonio/tempo/model/Server.kt
Normal file
39
app/src/main/java/com/cappielloantonio/tempo/model/Server.kt
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
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
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Keep
|
||||
@Parcelize
|
||||
@Entity(tableName = "server")
|
||||
data class Server(
|
||||
@PrimaryKey
|
||||
@ColumnInfo(name = "id")
|
||||
val serverId: String,
|
||||
|
||||
@ColumnInfo(name = "server_name")
|
||||
val serverName: String,
|
||||
|
||||
@ColumnInfo(name = "username")
|
||||
val username: String,
|
||||
|
||||
@ColumnInfo(name = "password")
|
||||
val password: String,
|
||||
|
||||
@ColumnInfo(name = "address")
|
||||
val address: String,
|
||||
|
||||
@ColumnInfo(name = "local_address")
|
||||
val localAddress: String?,
|
||||
|
||||
@ColumnInfo(name = "timestamp")
|
||||
val timestamp: Long,
|
||||
|
||||
@ColumnInfo(name = "low_security", defaultValue = "false")
|
||||
val isLowSecurity: Boolean
|
||||
) : Parcelable
|
||||
|
|
@ -0,0 +1,289 @@
|
|||
package com.cappielloantonio.tempo.model
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.annotation.Keep
|
||||
import androidx.media3.common.HeartRating
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaItem.RequestMetadata
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.room.ColumnInfo
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode
|
||||
import com.cappielloantonio.tempo.util.Constants
|
||||
import com.cappielloantonio.tempo.util.MusicUtil
|
||||
import com.cappielloantonio.tempo.util.Preferences.getImageSize
|
||||
import java.util.Date
|
||||
|
||||
@UnstableApi
|
||||
@Keep
|
||||
@Entity(tableName = "session_media_item")
|
||||
class SessionMediaItem() {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
@ColumnInfo(name = "index")
|
||||
var index: Int = 0
|
||||
|
||||
@ColumnInfo(name = "id")
|
||||
var id: String? = null
|
||||
|
||||
@ColumnInfo(name = "parent_id")
|
||||
var parentId: String? = null
|
||||
|
||||
@ColumnInfo(name = "is_dir")
|
||||
var isDir: Boolean = false
|
||||
|
||||
@ColumnInfo
|
||||
var title: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var album: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var artist: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var track: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var year: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var genre: String? = null
|
||||
|
||||
@ColumnInfo(name = "cover_art_id")
|
||||
var coverArtId: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var size: Long? = null
|
||||
|
||||
@ColumnInfo(name = "content_type")
|
||||
var contentType: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var suffix: String? = null
|
||||
|
||||
@ColumnInfo("transcoding_content_type")
|
||||
var transcodedContentType: String? = null
|
||||
|
||||
@ColumnInfo(name = "transcoded_suffix")
|
||||
var transcodedSuffix: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var duration: Int? = null
|
||||
|
||||
@ColumnInfo("bitrate")
|
||||
var bitrate: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var path: String? = null
|
||||
|
||||
@ColumnInfo(name = "is_video")
|
||||
var isVideo: Boolean = false
|
||||
|
||||
@ColumnInfo(name = "user_rating")
|
||||
var userRating: Int? = null
|
||||
|
||||
@ColumnInfo(name = "average_rating")
|
||||
var averageRating: Double? = null
|
||||
|
||||
@ColumnInfo(name = "play_count")
|
||||
var playCount: Long? = null
|
||||
|
||||
@ColumnInfo(name = "disc_number")
|
||||
var discNumber: Int? = null
|
||||
|
||||
@ColumnInfo
|
||||
var created: Date? = null
|
||||
|
||||
@ColumnInfo
|
||||
var starred: Date? = null
|
||||
|
||||
@ColumnInfo(name = "album_id")
|
||||
var albumId: String? = null
|
||||
|
||||
@ColumnInfo(name = "artist_id")
|
||||
var artistId: String? = null
|
||||
|
||||
@ColumnInfo
|
||||
var type: String? = null
|
||||
|
||||
@ColumnInfo(name = "bookmark_position")
|
||||
var bookmarkPosition: Long? = null
|
||||
|
||||
@ColumnInfo(name = "original_width")
|
||||
var originalWidth: Int? = null
|
||||
|
||||
@ColumnInfo(name = "original_height")
|
||||
var originalHeight: Int? = null
|
||||
|
||||
@ColumnInfo(name = "stream_id")
|
||||
var streamId: String? = null
|
||||
|
||||
@ColumnInfo(name = "stream_url")
|
||||
var streamUrl: String? = null
|
||||
|
||||
@ColumnInfo(name = "timestamp")
|
||||
var timestamp: Long? = null
|
||||
|
||||
constructor(child: Child) : this() {
|
||||
id = child.id
|
||||
parentId = child.parentId
|
||||
isDir = child.isDir
|
||||
title = child.title
|
||||
album = child.album
|
||||
artist = child.artist
|
||||
track = child.track
|
||||
year = child.year
|
||||
genre = child.genre
|
||||
coverArtId = child.coverArtId
|
||||
size = child.size
|
||||
contentType = child.contentType
|
||||
suffix = child.suffix
|
||||
transcodedContentType = child.transcodedContentType
|
||||
transcodedSuffix = child.transcodedSuffix
|
||||
duration = child.duration
|
||||
bitrate = child.bitrate
|
||||
path = child.path
|
||||
isVideo = child.isVideo
|
||||
userRating = child.userRating
|
||||
averageRating = child.averageRating
|
||||
playCount = child.playCount
|
||||
discNumber = child.discNumber
|
||||
created = child.created
|
||||
starred = child.starred
|
||||
albumId = child.albumId
|
||||
artistId = child.artistId
|
||||
type = Constants.MEDIA_TYPE_MUSIC
|
||||
bookmarkPosition = child.bookmarkPosition
|
||||
originalWidth = child.originalWidth
|
||||
originalHeight = child.originalHeight
|
||||
}
|
||||
|
||||
constructor(podcastEpisode: PodcastEpisode) : this() {
|
||||
id = podcastEpisode.id
|
||||
parentId = podcastEpisode.parentId
|
||||
isDir = podcastEpisode.isDir
|
||||
title = podcastEpisode.title
|
||||
album = podcastEpisode.album
|
||||
artist = podcastEpisode.artist
|
||||
year = podcastEpisode.year
|
||||
genre = podcastEpisode.genre
|
||||
coverArtId = podcastEpisode.coverArtId
|
||||
size = podcastEpisode.size
|
||||
contentType = podcastEpisode.contentType
|
||||
suffix = podcastEpisode.suffix
|
||||
duration = podcastEpisode.duration
|
||||
bitrate = podcastEpisode.bitrate
|
||||
path = podcastEpisode.path
|
||||
isVideo = podcastEpisode.isVideo
|
||||
created = podcastEpisode.created
|
||||
artistId = podcastEpisode.artistId
|
||||
streamId = podcastEpisode.streamId
|
||||
type = Constants.MEDIA_TYPE_PODCAST
|
||||
}
|
||||
|
||||
constructor(internetRadioStation: InternetRadioStation) : this() {
|
||||
id = internetRadioStation.id
|
||||
title = internetRadioStation.name
|
||||
streamUrl = internetRadioStation.streamUrl
|
||||
type = Constants.MEDIA_TYPE_RADIO
|
||||
}
|
||||
|
||||
fun getMediaItem(): MediaItem {
|
||||
val uri: Uri = getStreamUri()
|
||||
val artworkUri = Uri.parse(CustomGlideRequest.createUrl(coverArtId, getImageSize()))
|
||||
|
||||
val bundle = Bundle()
|
||||
bundle.putString("id", id)
|
||||
bundle.putString("parentId", parentId)
|
||||
bundle.putBoolean("isDir", isDir)
|
||||
bundle.putString("title", title)
|
||||
bundle.putString("album", album)
|
||||
bundle.putString("artist", artist)
|
||||
bundle.putInt("track", track ?: 0)
|
||||
bundle.putInt("year", year ?: 0)
|
||||
bundle.putString("genre", genre)
|
||||
bundle.putString("coverArtId", coverArtId)
|
||||
bundle.putLong("size", size ?: 0)
|
||||
bundle.putString("contentType", contentType)
|
||||
bundle.putString("suffix", suffix)
|
||||
bundle.putString("transcodedContentType", transcodedContentType)
|
||||
bundle.putString("transcodedSuffix", transcodedSuffix)
|
||||
bundle.putInt("duration", duration ?: 0)
|
||||
bundle.putInt("bitrate", bitrate ?: 0)
|
||||
bundle.putString("path", path)
|
||||
bundle.putBoolean("isVideo", isVideo)
|
||||
bundle.putInt("userRating", userRating ?: 0)
|
||||
bundle.putDouble("averageRating", averageRating ?: .0)
|
||||
bundle.putLong("playCount", playCount ?: 0)
|
||||
bundle.putInt("discNumber", discNumber ?: 0)
|
||||
bundle.putLong("created", created?.time ?: 0)
|
||||
bundle.putLong("starred", starred?.time ?: 0)
|
||||
bundle.putString("albumId", albumId)
|
||||
bundle.putString("artistId", artistId)
|
||||
bundle.putString("type", Constants.MEDIA_TYPE_MUSIC)
|
||||
bundle.putLong("bookmarkPosition", bookmarkPosition ?: 0)
|
||||
bundle.putInt("originalWidth", originalWidth ?: 0)
|
||||
bundle.putInt("originalHeight", originalHeight ?: 0)
|
||||
bundle.putString("uri", uri.toString())
|
||||
|
||||
return MediaItem.Builder()
|
||||
.setMediaId(id!!)
|
||||
.setMediaMetadata(
|
||||
MediaMetadata.Builder()
|
||||
.setTitle(title)
|
||||
.setTrackNumber(track ?: 0)
|
||||
.setDiscNumber(discNumber ?: 0)
|
||||
.setReleaseYear(year ?: 0)
|
||||
.setAlbumTitle(album)
|
||||
.setArtist(artist)
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(HeartRating(starred != null))
|
||||
.setSupportedCommands(
|
||||
listOf(
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||
)
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
.build()
|
||||
)
|
||||
.setRequestMetadata(
|
||||
RequestMetadata.Builder()
|
||||
.setMediaUri(uri)
|
||||
.setExtras(bundle)
|
||||
.build()
|
||||
)
|
||||
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
|
||||
.setUri(uri)
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getStreamUri(): Uri {
|
||||
return when (type) {
|
||||
Constants.MEDIA_TYPE_MUSIC -> {
|
||||
MusicUtil.getStreamUri(id)
|
||||
}
|
||||
|
||||
Constants.MEDIA_TYPE_PODCAST -> {
|
||||
MusicUtil.getStreamUri(streamId)
|
||||
}
|
||||
|
||||
Constants.MEDIA_TYPE_RADIO -> {
|
||||
Uri.parse(streamUrl)
|
||||
}
|
||||
|
||||
else -> {
|
||||
MusicUtil.getStreamUri(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.interfaces.DecadesCallback;
|
||||
import com.cappielloantonio.tempo.interfaces.MediaCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumInfo;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class AlbumRepository {
|
||||
public MutableLiveData<List<AlbumID3>> getAlbums(String type, int size, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<AlbumID3>> listLiveAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getAlbumList2(type, size, 0, fromYear, toYear)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful()
|
||||
&& response.body() != null
|
||||
&& response.body().getSubsonicResponse().getAlbumList2() != null
|
||||
&& response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
|
||||
|
||||
listLiveAlbums.setValue(response.body().getSubsonicResponse().getAlbumList2().getAlbums());
|
||||
} else {
|
||||
Log.e("AlbumRepository", "API Error on getAlbums. HTTP Code: " + response.code());
|
||||
listLiveAlbums.setValue(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.e("AlbumRepository", "Network Failure on getAlbums: " + t.getMessage());
|
||||
listLiveAlbums.setValue(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
|
||||
return listLiveAlbums;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<AlbumID3>> getStarredAlbums(boolean random, int size) {
|
||||
MutableLiveData<List<AlbumID3>> starredAlbums = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null) {
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getStarred2().getAlbums();
|
||||
|
||||
if (albums != null) {
|
||||
if (random) {
|
||||
Collections.shuffle(albums);
|
||||
starredAlbums.setValue(albums.subList(0, Math.min(size, albums.size())));
|
||||
} else {
|
||||
starredAlbums.setValue(albums);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return starredAlbums;
|
||||
}
|
||||
|
||||
public void setRating(String id, int rating) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.setRating(id, rating)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getAlbumTracks(String id) {
|
||||
MutableLiveData<List<Child>> albumTracks = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getAlbum(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> tracks = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
||||
if (response.body().getSubsonicResponse().getAlbum().getSongs() != null) {
|
||||
tracks.addAll(response.body().getSubsonicResponse().getAlbum().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
albumTracks.setValue(tracks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return albumTracks;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<AlbumID3>> getArtistAlbums(String id) {
|
||||
MutableLiveData<List<AlbumID3>> artistsAlbum = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null && response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
|
||||
albums.sort(Comparator.comparing(AlbumID3::getYear));
|
||||
Collections.reverse(albums);
|
||||
artistsAlbum.setValue(albums);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return artistsAlbum;
|
||||
}
|
||||
|
||||
public MutableLiveData<AlbumID3> getAlbum(String id) {
|
||||
MutableLiveData<AlbumID3> album = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getAlbum(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbum() != null) {
|
||||
album.setValue(response.body().getSubsonicResponse().getAlbum());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return album;
|
||||
}
|
||||
|
||||
public MutableLiveData<AlbumInfo> getAlbumInfo(String id) {
|
||||
MutableLiveData<AlbumInfo> albumInfo = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getAlbumInfo2(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumInfo() != null) {
|
||||
albumInfo.setValue(response.body().getSubsonicResponse().getAlbumInfo());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return albumInfo;
|
||||
}
|
||||
|
||||
public void getInstantMix(AlbumID3 album, int count, MediaCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(album.getId(), count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
|
||||
callback.onLoadMedia(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onLoadMedia(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Integer>> getDecades() {
|
||||
MutableLiveData<List<Integer>> decades = new MutableLiveData<>();
|
||||
|
||||
getFirstAlbum(new DecadesCallback() {
|
||||
@Override
|
||||
public void onLoadYear(int first) {
|
||||
getLastAlbum(new DecadesCallback() {
|
||||
@Override
|
||||
public void onLoadYear(int last) {
|
||||
if (first != -1 && last != -1) {
|
||||
List<Integer> decadeList = new ArrayList();
|
||||
|
||||
int startDecade = first - (first % 10);
|
||||
int lastDecade = last - (last % 10);
|
||||
|
||||
while (startDecade <= lastDecade) {
|
||||
decadeList.add(startDecade);
|
||||
startDecade = startDecade + 10;
|
||||
}
|
||||
|
||||
decades.setValue(decadeList);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return decades;
|
||||
}
|
||||
|
||||
private void getFirstAlbum(DecadesCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getAlbumList2("byYear", 1, 0, 1900, Calendar.getInstance().get(Calendar.YEAR))
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
|
||||
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onLoadYear(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void getLastAlbum(DecadesCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getAlbumList2("byYear", 1, 0, Calendar.getInstance().get(Calendar.YEAR), 1900)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getAlbumList2() != null && response.body().getSubsonicResponse().getAlbumList2().getAlbums() != null) {
|
||||
if (!response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty() && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
|
||||
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
|
||||
} else {
|
||||
callback.onLoadYear(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onLoadYear(-1);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,387 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.AlbumID3;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ArtistInfo2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.IndexID3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class ArtistRepository {
|
||||
private final AlbumRepository albumRepository;
|
||||
|
||||
public ArtistRepository() {
|
||||
this.albumRepository = new AlbumRepository();
|
||||
}
|
||||
|
||||
public void getArtistAllSongs(String artistId, ArtistSongsCallback callback) {
|
||||
Log.d("ArtistSync", "Getting albums for artist: " + artistId);
|
||||
|
||||
// Get the artist info first, which contains the albums
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(artistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null &&
|
||||
response.body().getSubsonicResponse().getArtist() != null &&
|
||||
response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
|
||||
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
|
||||
Log.d("ArtistSync", "Got albums directly: " + albums.size());
|
||||
|
||||
if (!albums.isEmpty()) {
|
||||
fetchAllAlbumSongsWithCallback(albums, callback);
|
||||
} else {
|
||||
Log.d("ArtistSync", "No albums found in artist response");
|
||||
callback.onSongsCollected(new ArrayList<>());
|
||||
}
|
||||
} else {
|
||||
Log.d("ArtistSync", "Failed to get artist info");
|
||||
callback.onSongsCollected(new ArrayList<>());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d("ArtistSync", "Error getting artist info: " + t.getMessage());
|
||||
callback.onSongsCollected(new ArrayList<>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void fetchAllAlbumSongsWithCallback(List<AlbumID3> albums, ArtistSongsCallback callback) {
|
||||
if (albums == null || albums.isEmpty()) {
|
||||
Log.d("ArtistSync", "No albums to process");
|
||||
callback.onSongsCollected(new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
List<Child> allSongs = new ArrayList<>();
|
||||
AtomicInteger remainingAlbums = new AtomicInteger(albums.size());
|
||||
Log.d("ArtistSync", "Processing " + albums.size() + " albums");
|
||||
|
||||
for (AlbumID3 album : albums) {
|
||||
Log.d("ArtistSync", "Getting tracks for album: " + album.getName());
|
||||
MutableLiveData<List<Child>> albumTracks = albumRepository.getAlbumTracks(album.getId());
|
||||
albumTracks.observeForever(songs -> {
|
||||
Log.d("ArtistSync", "Got " + (songs != null ? songs.size() : 0) + " songs from album");
|
||||
if (songs != null) {
|
||||
allSongs.addAll(songs);
|
||||
}
|
||||
albumTracks.removeObservers(null);
|
||||
|
||||
int remaining = remainingAlbums.decrementAndGet();
|
||||
Log.d("ArtistSync", "Remaining albums: " + remaining);
|
||||
|
||||
if (remaining == 0) {
|
||||
Log.d("ArtistSync", "All albums processed. Total songs: " + allSongs.size());
|
||||
callback.onSongsCollected(allSongs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public interface ArtistSongsCallback {
|
||||
void onSongsCollected(List<Child> songs);
|
||||
}
|
||||
|
||||
public MutableLiveData<List<ArtistID3>> getStarredArtists(boolean random, int size) {
|
||||
MutableLiveData<List<ArtistID3>> starredArtists = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null) {
|
||||
List<ArtistID3> artists = response.body().getSubsonicResponse().getStarred2().getArtists();
|
||||
|
||||
if (artists != null) {
|
||||
if (!random) {
|
||||
getArtistInfo(artists, starredArtists);
|
||||
} else {
|
||||
Collections.shuffle(artists);
|
||||
getArtistInfo(artists.subList(0, Math.min(size, artists.size())), starredArtists);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return starredArtists;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<ArtistID3>> getArtists(boolean random, int size) {
|
||||
MutableLiveData<List<ArtistID3>> listLiveArtists = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtists()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
List<ArtistID3> artists = new ArrayList<>();
|
||||
|
||||
if(response.body().getSubsonicResponse().getArtists() != null && response.body().getSubsonicResponse().getArtists().getIndices() != null) {
|
||||
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
|
||||
if(index != null && index.getArtists() != null) {
|
||||
artists.addAll(index.getArtists());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (random) {
|
||||
Collections.shuffle(artists);
|
||||
getArtistInfo(artists.subList(0, artists.size() / size > 0 ? size : artists.size()), listLiveArtists);
|
||||
} else {
|
||||
listLiveArtists.setValue(artists);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
|
||||
return listLiveArtists;
|
||||
}
|
||||
|
||||
/*
|
||||
* Method that returns essential artist information (cover, album number, etc.)
|
||||
*/
|
||||
public void getArtistInfo(List<ArtistID3> artists, MutableLiveData<List<ArtistID3>> list) {
|
||||
List<ArtistID3> liveArtists = list.getValue();
|
||||
if (liveArtists == null) liveArtists = new ArrayList<>();
|
||||
list.setValue(liveArtists);
|
||||
|
||||
for (ArtistID3 artist : artists) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(artist.getId())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null) {
|
||||
addToMutableLiveData(list, response.body().getSubsonicResponse().getArtist());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public MutableLiveData<ArtistID3> getArtistInfo(String id) {
|
||||
MutableLiveData<ArtistID3> artist = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null) {
|
||||
artist.setValue(response.body().getSubsonicResponse().getArtist());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
public MutableLiveData<ArtistInfo2> getArtistFullInfo(String id) {
|
||||
MutableLiveData<ArtistInfo2> artistFullInfo = new MutableLiveData<>(null);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtistInfo2(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtistInfo2() != null) {
|
||||
artistFullInfo.setValue(response.body().getSubsonicResponse().getArtistInfo2());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return artistFullInfo;
|
||||
}
|
||||
|
||||
public void setRating(String id, int rating) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.setRating(id, rating)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<ArtistID3> getArtist(String id) {
|
||||
MutableLiveData<ArtistID3> artist = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getArtist() != null) {
|
||||
artist.setValue(response.body().getSubsonicResponse().getArtist());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return artist;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(ArtistID3 artist, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(artist.getId(), count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSong(ArtistID3 artist, int count) {
|
||||
MutableLiveData<List<Child>> randomSongs = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getArtist(artist.getId())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null &&
|
||||
response.body().getSubsonicResponse().getArtist() != null &&
|
||||
response.body().getSubsonicResponse().getArtist().getAlbums() != null) {
|
||||
|
||||
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
|
||||
Log.d("ArtistRepository", "Got albums directly: " + albums.size());
|
||||
if (albums.isEmpty()) {
|
||||
Log.d("ArtistRepository", "No albums found in artist response");
|
||||
return;
|
||||
}
|
||||
|
||||
Collections.shuffle(albums);
|
||||
int[] counts = albums.stream().mapToInt(AlbumID3::getSongCount).toArray();
|
||||
Arrays.parallelPrefix(counts, Integer::sum);
|
||||
int albumLimit = 0;
|
||||
int multiplier = 4; // get more than the limit so we can shuffle them
|
||||
while (albumLimit < albums.size() && counts[albumLimit] < count * multiplier)
|
||||
albumLimit++;
|
||||
Log.d("ArtistRepository", String.format("Retaining %d/%d albums", albumLimit, albums.size()));
|
||||
|
||||
fetchAllAlbumSongsWithCallback(albums.stream().limit(albumLimit).collect(Collectors.toList()), songs -> {
|
||||
Collections.shuffle(songs);
|
||||
randomSongs.setValue(songs.stream().limit(count).collect(Collectors.toList()));
|
||||
});
|
||||
} else {
|
||||
Log.d("ArtistRepository", "Failed to get artist info");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Log.d("ArtistRepository", "Error getting artist info: " + t.getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
return randomSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getTopSongs(String artistName, int count) {
|
||||
MutableLiveData<List<Child>> topSongs = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getTopSongs(artistName, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getTopSongs() != null && response.body().getSubsonicResponse().getTopSongs().getSongs() != null) {
|
||||
topSongs.setValue(response.body().getSubsonicResponse().getTopSongs().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return topSongs;
|
||||
}
|
||||
|
||||
private void addToMutableLiveData(MutableLiveData<List<ArtistID3>> liveData, ArtistID3 artist) {
|
||||
List<ArtistID3> liveArtists = liveData.getValue();
|
||||
if (liveArtists != null) liveArtists.add(artist);
|
||||
liveData.setValue(liveArtists);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,39 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.ChronologyDao;
|
||||
import com.cappielloantonio.tempo.model.Chronology;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
|
||||
public class ChronologyRepository {
|
||||
private final ChronologyDao chronologyDao = AppDatabase.getInstance().chronologyDao();
|
||||
|
||||
public LiveData<List<Chronology>> getChronology(String server, long start, long end) {
|
||||
return chronologyDao.getAllFrom(start, end, server);
|
||||
}
|
||||
|
||||
public void insert(Chronology item) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(chronologyDao, item);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final ChronologyDao chronologyDao;
|
||||
private final Chronology item;
|
||||
|
||||
public InsertThreadSafe(ChronologyDao chronologyDao, Chronology item) {
|
||||
this.chronologyDao = chronologyDao;
|
||||
this.item = item;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
chronologyDao.insert(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Directory;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Indexes;
|
||||
import com.cappielloantonio.tempo.subsonic.models.MusicFolder;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class DirectoryRepository {
|
||||
private static final String TAG = "DirectoryRepository";
|
||||
|
||||
public MutableLiveData<List<MusicFolder>> getMusicFolders() {
|
||||
MutableLiveData<List<MusicFolder>> liveMusicFolders = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicFolders()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getMusicFolders() != null) {
|
||||
liveMusicFolders.setValue(response.body().getSubsonicResponse().getMusicFolders().getMusicFolders());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return liveMusicFolders;
|
||||
}
|
||||
|
||||
public MutableLiveData<Indexes> getIndexes(String musicFolderId, Long ifModifiedSince) {
|
||||
MutableLiveData<Indexes> liveIndexes = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getIndexes(musicFolderId, ifModifiedSince)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getIndexes() != null) {
|
||||
liveIndexes.setValue(response.body().getSubsonicResponse().getIndexes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return liveIndexes;
|
||||
}
|
||||
|
||||
public MutableLiveData<Directory> getMusicDirectory(String id) {
|
||||
MutableLiveData<Directory> liveMusicDirectory = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getMusicDirectory(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getDirectory() != null) {
|
||||
liveMusicDirectory.setValue(response.body().getSubsonicResponse().getDirectory());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
|
||||
return liveMusicDirectory;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,213 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.DownloadDao;
|
||||
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.model.Download;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class DownloadRepository {
|
||||
private final DownloadDao downloadDao = AppDatabase.getInstance().downloadDao();
|
||||
|
||||
public LiveData<List<Download>> getLiveDownload() {
|
||||
return downloadDao.getAll();
|
||||
}
|
||||
|
||||
public List<Download> getAllDownloads() {
|
||||
GetAllDownloadsThreadSafe getDownloads = new GetAllDownloadsThreadSafe(downloadDao);
|
||||
Thread thread = new Thread(getDownloads);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return getDownloads.getDownloads();
|
||||
}
|
||||
|
||||
public Download getDownload(String id) {
|
||||
Download download = null;
|
||||
|
||||
GetDownloadThreadSafe getDownloadThreadSafe = new GetDownloadThreadSafe(downloadDao, id);
|
||||
Thread thread = new Thread(getDownloadThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
download = getDownloadThreadSafe.getDownload();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return download;
|
||||
}
|
||||
|
||||
private static class GetAllDownloadsThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private List<Download> downloads;
|
||||
|
||||
public GetAllDownloadsThreadSafe(DownloadDao downloadDao) {
|
||||
this.downloadDao = downloadDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloads = downloadDao.getAllSync();
|
||||
}
|
||||
|
||||
public List<Download> getDownloads() {
|
||||
return downloads;
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetDownloadThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final String id;
|
||||
private Download download;
|
||||
|
||||
public GetDownloadThreadSafe(DownloadDao downloadDao, String id) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
download = downloadDao.getOne(id);
|
||||
}
|
||||
|
||||
public Download getDownload() {
|
||||
return download;
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(Download download) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(downloadDao, download);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final Download download;
|
||||
|
||||
public InsertThreadSafe(DownloadDao downloadDao, Download download) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.download = download;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.insert(download);
|
||||
}
|
||||
}
|
||||
|
||||
public void update(String id) {
|
||||
UpdateThreadSafe update = new UpdateThreadSafe(downloadDao, id);
|
||||
Thread thread = new Thread(update);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class UpdateThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final String id;
|
||||
|
||||
public UpdateThreadSafe(DownloadDao downloadDao, String id) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.update(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void insertAll(List<Download> downloads) {
|
||||
InsertAllThreadSafe insertAll = new InsertAllThreadSafe(downloadDao, downloads);
|
||||
Thread thread = new Thread(insertAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertAllThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final List<Download> downloads;
|
||||
|
||||
public InsertAllThreadSafe(DownloadDao downloadDao, List<Download> downloads) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.downloads = downloads;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.insertAll(downloads);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAll() {
|
||||
DeleteAllThreadSafe deleteAll = new DeleteAllThreadSafe(downloadDao);
|
||||
Thread thread = new Thread(deleteAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class DeleteAllThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
|
||||
public DeleteAllThreadSafe(DownloadDao downloadDao) {
|
||||
this.downloadDao = downloadDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(String id) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(downloadDao, id);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void delete(List<String> ids) {
|
||||
DeleteMultipleThreadSafe delete = new DeleteMultipleThreadSafe(downloadDao, ids);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final String id;
|
||||
|
||||
public DeleteThreadSafe(DownloadDao downloadDao, String id) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteMultipleThreadSafe implements Runnable {
|
||||
private final DownloadDao downloadDao;
|
||||
private final List<String> ids;
|
||||
|
||||
public DeleteMultipleThreadSafe(DownloadDao downloadDao, List<String> ids) {
|
||||
this.downloadDao = downloadDao;
|
||||
this.ids = ids;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
downloadDao.deleteByIds(ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.FavoriteDao;
|
||||
import com.cappielloantonio.tempo.interfaces.StarCallback;
|
||||
import com.cappielloantonio.tempo.model.Favorite;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class FavoriteRepository {
|
||||
private final FavoriteDao favoriteDao = AppDatabase.getInstance().favoriteDao();
|
||||
|
||||
public void star(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.star(id, albumId, artistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful()) {
|
||||
starCallback.onSuccess();
|
||||
} else {
|
||||
starCallback.onError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
starCallback.onError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void unstar(String id, String albumId, String artistId, StarCallback starCallback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.unstar(id, albumId, artistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful()) {
|
||||
starCallback.onSuccess();
|
||||
} else {
|
||||
starCallback.onError();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
starCallback.onError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public List<Favorite> getFavorites() {
|
||||
List<Favorite> favorites = new ArrayList<>();
|
||||
|
||||
GetAllThreadSafe getAllThreadSafe = new GetAllThreadSafe(favoriteDao);
|
||||
Thread thread = new Thread(getAllThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
favorites = getAllThreadSafe.getFavorites();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return favorites;
|
||||
}
|
||||
|
||||
private static class GetAllThreadSafe implements Runnable {
|
||||
private final FavoriteDao favoriteDao;
|
||||
private List<Favorite> favorites = new ArrayList<>();
|
||||
|
||||
public GetAllThreadSafe(FavoriteDao favoriteDao) {
|
||||
this.favoriteDao = favoriteDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
favorites = favoriteDao.getAll();
|
||||
}
|
||||
|
||||
public List<Favorite> getFavorites() {
|
||||
return favorites;
|
||||
}
|
||||
}
|
||||
|
||||
public void starLater(String id, String albumId, String artistId, boolean toStar) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(favoriteDao, new Favorite(System.currentTimeMillis(), id, albumId, artistId, toStar));
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final FavoriteDao favoriteDao;
|
||||
private final Favorite favorite;
|
||||
|
||||
public InsertThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||
this.favoriteDao = favoriteDao;
|
||||
this.favorite = favorite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
favoriteDao.insert(favorite);
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(Favorite favorite) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(favoriteDao, favorite);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final FavoriteDao favoriteDao;
|
||||
private final Favorite favorite;
|
||||
|
||||
public DeleteThreadSafe(FavoriteDao favoriteDao, Favorite favorite) {
|
||||
this.favoriteDao = favoriteDao;
|
||||
this.favorite = favorite;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
favoriteDao.delete(favorite);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Genre;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class GenreRepository {
|
||||
public MutableLiveData<List<Genre>> getGenres(boolean random, int size) {
|
||||
MutableLiveData<List<Genre>> genres = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getGenres()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null && response.body().getSubsonicResponse().getGenres() != null) {
|
||||
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
|
||||
|
||||
if (genreList == null || genreList.isEmpty()) {
|
||||
genres.setValue(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
||||
if (random) {
|
||||
Collections.shuffle(genreList);
|
||||
}
|
||||
|
||||
if (size != -1) {
|
||||
genres.setValue(genreList.subList(0, Math.min(size, genreList.size())));
|
||||
} else {
|
||||
genres.setValue(genreList.stream().sorted(Comparator.comparing(Genre::getGenre)).collect(Collectors.toList()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return genres;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.LyricsDao;
|
||||
import com.cappielloantonio.tempo.model.LyricsCache;
|
||||
|
||||
public class LyricsRepository {
|
||||
private final LyricsDao lyricsDao = AppDatabase.getInstance().lyricsDao();
|
||||
|
||||
public LyricsCache getLyrics(String songId) {
|
||||
GetLyricsThreadSafe getLyricsThreadSafe = new GetLyricsThreadSafe(lyricsDao, songId);
|
||||
Thread thread = new Thread(getLyricsThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
return getLyricsThreadSafe.getLyrics();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public LiveData<LyricsCache> observeLyrics(String songId) {
|
||||
return lyricsDao.observeOne(songId);
|
||||
}
|
||||
|
||||
public void insert(LyricsCache lyricsCache) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(lyricsDao, lyricsCache);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void delete(String songId) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(lyricsDao, songId);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class GetLyricsThreadSafe implements Runnable {
|
||||
private final LyricsDao lyricsDao;
|
||||
private final String songId;
|
||||
private LyricsCache lyricsCache;
|
||||
|
||||
public GetLyricsThreadSafe(LyricsDao lyricsDao, String songId) {
|
||||
this.lyricsDao = lyricsDao;
|
||||
this.songId = songId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
lyricsCache = lyricsDao.getOne(songId);
|
||||
}
|
||||
|
||||
public LyricsCache getLyrics() {
|
||||
return lyricsCache;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final LyricsDao lyricsDao;
|
||||
private final LyricsCache lyricsCache;
|
||||
|
||||
public InsertThreadSafe(LyricsDao lyricsDao, LyricsCache lyricsCache) {
|
||||
this.lyricsDao = lyricsDao;
|
||||
this.lyricsCache = lyricsCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
lyricsDao.insert(lyricsCache);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final LyricsDao lyricsDao;
|
||||
private final String songId;
|
||||
|
||||
public DeleteThreadSafe(LyricsDao lyricsDao, String songId) {
|
||||
this.lyricsDao = lyricsDao;
|
||||
this.songId = songId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
lyricsDao.delete(songId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.LyricsList;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class OpenRepository {
|
||||
public MutableLiveData<LyricsList> getLyricsBySongId(String id) {
|
||||
MutableLiveData<LyricsList> lyricsList = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getOpenClient()
|
||||
.getLyricsBySongId(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyricsList() != null) {
|
||||
lyricsList.setValue(response.body().getSubsonicResponse().getLyricsList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return lyricsList;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,229 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import static android.provider.Settings.System.getString;
|
||||
|
||||
import android.provider.Settings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.R;
|
||||
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;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class PlaylistRepository {
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
private final PlaylistDao playlistDao = AppDatabase.getInstance().playlistDao();
|
||||
public MutableLiveData<List<Playlist>> getPlaylists(boolean random, int size) {
|
||||
MutableLiveData<List<Playlist>> listLivePlaylists = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylists()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylists() != null && response.body().getSubsonicResponse().getPlaylists().getPlaylists() != null) {
|
||||
List<Playlist> playlists = response.body().getSubsonicResponse().getPlaylists().getPlaylists();
|
||||
|
||||
if (random) {
|
||||
Collections.shuffle(playlists);
|
||||
listLivePlaylists.setValue(playlists.subList(0, Math.min(playlists.size(), size)));
|
||||
} else {
|
||||
listLivePlaylists.setValue(playlists);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
|
||||
return listLivePlaylists;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getPlaylistSongs(String id) {
|
||||
MutableLiveData<List<Child>> listLivePlaylistSongs = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlaylist() != null) {
|
||||
List<Child> songs = response.body().getSubsonicResponse().getPlaylist().getEntries();
|
||||
listLivePlaylistSongs.setValue(songs);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
}
|
||||
});
|
||||
|
||||
return listLivePlaylistSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<Playlist> getPlaylist(String id) {
|
||||
MutableLiveData<Playlist> playlistLiveData = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.getPlaylist(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful()
|
||||
&& response.body() != null
|
||||
&& response.body().getSubsonicResponse().getPlaylist() != null) {
|
||||
playlistLiveData.setValue(response.body().getSubsonicResponse().getPlaylist());
|
||||
} else {
|
||||
playlistLiveData.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
playlistLiveData.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return playlistLiveData;
|
||||
}
|
||||
|
||||
public void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
|
||||
if (songsId.isEmpty()) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_all_skipped), Toast.LENGTH_SHORT).show();
|
||||
} else{
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.updatePlaylist(playlistId, null, true, songsId, null)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_success), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
Toast.makeText(App.getContext(), App.getContext().getString(R.string.playlist_chooser_dialog_toast_add_failure), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void createPlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.createPlaylist(playlistId, name, songsId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updatePlaylist(String playlistId, String name, ArrayList<String> songsId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.deletePlaylist(playlistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
createPlaylist(null, name, songsId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deletePlaylist(String playlistId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPlaylistClient()
|
||||
.deletePlaylist(playlistId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
public LiveData<List<Playlist>> getPinnedPlaylists() {
|
||||
return playlistDao.getAll();
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
public void insert(Playlist playlist) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(playlistDao, playlist);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
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.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastChannel;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class PodcastRepository {
|
||||
private static final String TAG = "PodcastRepository";
|
||||
|
||||
public MutableLiveData<List<PodcastChannel>> getPodcastChannels(boolean includeEpisodes, String channelId) {
|
||||
MutableLiveData<List<PodcastChannel>> livePodcastChannel = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.getPodcasts(includeEpisodes, channelId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPodcasts() != null) {
|
||||
livePodcastChannel.setValue(response.body().getSubsonicResponse().getPodcasts().getChannels());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return livePodcastChannel;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<PodcastEpisode>> getNewestPodcastEpisodes(int count) {
|
||||
MutableLiveData<List<PodcastEpisode>> liveNewestPodcastEpisodes = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.getNewestPodcasts(count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getNewestPodcasts() != null) {
|
||||
liveNewestPodcastEpisodes.setValue(response.body().getSubsonicResponse().getNewestPodcasts().getEpisodes());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return liveNewestPodcastEpisodes;
|
||||
}
|
||||
|
||||
public void refreshPodcasts() {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.refreshPodcasts()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void createPodcastChannel(String url) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.createPodcastChannel(url)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deletePodcastChannel(String channelId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.deletePodcastChannel(channelId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deletePodcastEpisode(String episodeId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.deletePodcastEpisode(episodeId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void downloadPodcastEpisode(String episodeId) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getPodcastClient()
|
||||
.downloadPodcastEpisode(episodeId)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,378 @@
|
|||
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.QueueDao;
|
||||
import com.cappielloantonio.tempo.model.Queue;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PlayQueue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class QueueRepository {
|
||||
private static final String TAG = "QueueRepository";
|
||||
|
||||
private final QueueDao queueDao = AppDatabase.getInstance().queueDao();
|
||||
|
||||
public LiveData<List<Queue>> getLiveQueue() {
|
||||
return queueDao.getAll();
|
||||
}
|
||||
|
||||
public List<Child> getMedia() {
|
||||
List<Child> media = new ArrayList<>();
|
||||
|
||||
GetMediaThreadSafe getMedia = new GetMediaThreadSafe(queueDao);
|
||||
Thread thread = new Thread(getMedia);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
media = getMedia.getMedia().stream()
|
||||
.map(Child.class::cast)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return media;
|
||||
}
|
||||
|
||||
public MutableLiveData<PlayQueue> getPlayQueue() {
|
||||
MutableLiveData<PlayQueue> playQueue = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBookmarksClient()
|
||||
.getPlayQueue()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getPlayQueue() != null) {
|
||||
playQueue.setValue(response.body().getSubsonicResponse().getPlayQueue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
playQueue.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return playQueue;
|
||||
}
|
||||
|
||||
public void savePlayQueue(List<String> ids, String current, long position) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBookmarksClient()
|
||||
.savePlayQueue(ids, current, position)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void insert(Child media, boolean reset, int afterIndex) {
|
||||
try {
|
||||
List<Queue> mediaList = new ArrayList<>();
|
||||
|
||||
if (!reset) {
|
||||
GetMediaThreadSafe getMediaThreadSafe = new GetMediaThreadSafe(queueDao);
|
||||
Thread getMediaThread = new Thread(getMediaThreadSafe);
|
||||
getMediaThread.start();
|
||||
getMediaThread.join();
|
||||
|
||||
mediaList = getMediaThreadSafe.getMedia();
|
||||
}
|
||||
|
||||
Queue queueItem = new Queue(media);
|
||||
mediaList.add(afterIndex, queueItem);
|
||||
|
||||
for (int i = 0; i < mediaList.size(); i++) {
|
||||
mediaList.get(i).setTrackOrder(i);
|
||||
}
|
||||
|
||||
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
|
||||
delete.start();
|
||||
delete.join();
|
||||
|
||||
Thread insertAll = new Thread(new InsertAllThreadSafe(queueDao, mediaList));
|
||||
insertAll.start();
|
||||
insertAll.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMediaInQueue(List<Queue> queue, Child media) {
|
||||
if (queue == null || media == null) return false;
|
||||
|
||||
return queue.stream().anyMatch(queueItem ->
|
||||
queueItem != null && media.getId() != null &&
|
||||
queueItem.getId().equals(media.getId())
|
||||
);
|
||||
}
|
||||
|
||||
public void insertAll(List<Child> toAdd, boolean reset, int afterIndex) {
|
||||
try {
|
||||
List<Queue> media = new ArrayList<>();
|
||||
|
||||
if (!reset) {
|
||||
GetMediaThreadSafe getMediaThreadSafe = new GetMediaThreadSafe(queueDao);
|
||||
Thread getMediaThread = new Thread(getMediaThreadSafe);
|
||||
getMediaThread.start();
|
||||
getMediaThread.join();
|
||||
|
||||
media = getMediaThreadSafe.getMedia();
|
||||
}
|
||||
|
||||
List<Child> filteredToAdd = toAdd;
|
||||
final List<Queue> finalMedia = media;
|
||||
filteredToAdd = toAdd.stream()
|
||||
.filter(child -> !isMediaInQueue(finalMedia, child))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < filteredToAdd.size(); i++) {
|
||||
Queue queueItem = new Queue(filteredToAdd.get(i));
|
||||
media.add(afterIndex + i, queueItem);
|
||||
}
|
||||
|
||||
for (int i = 0; i < media.size(); i++) {
|
||||
media.get(i).setTrackOrder(i);
|
||||
}
|
||||
|
||||
Thread delete = new Thread(new DeleteAllThreadSafe(queueDao));
|
||||
delete.start();
|
||||
delete.join();
|
||||
|
||||
Thread insertAll = new Thread(new InsertAllThreadSafe(queueDao, media));
|
||||
insertAll.start();
|
||||
insertAll.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void delete(int position) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(queueDao, position);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void deleteAll() {
|
||||
DeleteAllThreadSafe deleteAll = new DeleteAllThreadSafe(queueDao);
|
||||
Thread thread = new Thread(deleteAll);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public int count() {
|
||||
int count = 0;
|
||||
|
||||
CountThreadSafe countThread = new CountThreadSafe(queueDao);
|
||||
Thread thread = new Thread(countThread);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
count = countThread.getCount();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setLastPlayedTimestamp(String id) {
|
||||
SetLastPlayedTimestampThreadSafe timestamp = new SetLastPlayedTimestampThreadSafe(queueDao, id);
|
||||
Thread thread = new Thread(timestamp);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setPlayingPausedTimestamp(String id, long ms) {
|
||||
SetPlayingPausedTimestampThreadSafe timestamp = new SetPlayingPausedTimestampThreadSafe(queueDao, id, ms);
|
||||
Thread thread = new Thread(timestamp);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public int getLastPlayedMediaIndex() {
|
||||
int index = 0;
|
||||
|
||||
GetLastPlayedMediaThreadSafe getLastPlayedMediaThreadSafe = new GetLastPlayedMediaThreadSafe(queueDao);
|
||||
Thread thread = new Thread(getLastPlayedMediaThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
Queue lastMediaPlayed = getLastPlayedMediaThreadSafe.getQueueItem();
|
||||
index = lastMediaPlayed.getTrackOrder();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
public long getLastPlayedMediaTimestamp() {
|
||||
long timestamp = 0;
|
||||
|
||||
GetLastPlayedMediaThreadSafe getLastPlayedMediaThreadSafe = new GetLastPlayedMediaThreadSafe(queueDao);
|
||||
Thread thread = new Thread(getLastPlayedMediaThreadSafe);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
Queue lastMediaPlayed = getLastPlayedMediaThreadSafe.getQueueItem();
|
||||
timestamp = lastMediaPlayed.getPlayingChanged();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
private static class GetMediaThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private List<Queue> media;
|
||||
|
||||
public GetMediaThreadSafe(QueueDao queueDao) {
|
||||
this.queueDao = queueDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
media = queueDao.getAllSimple();
|
||||
}
|
||||
|
||||
public List<Queue> getMedia() {
|
||||
return media;
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertAllThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private final List<Queue> media;
|
||||
|
||||
public InsertAllThreadSafe(QueueDao queueDao, List<Queue> media) {
|
||||
this.queueDao = queueDao;
|
||||
this.media = media;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
queueDao.insertAll(media);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private final int position;
|
||||
|
||||
public DeleteThreadSafe(QueueDao queueDao, int position) {
|
||||
this.queueDao = queueDao;
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
queueDao.delete(position);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteAllThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
|
||||
public DeleteAllThreadSafe(QueueDao queueDao) {
|
||||
this.queueDao = queueDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
queueDao.deleteAll();
|
||||
}
|
||||
}
|
||||
|
||||
private static class CountThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private int count = 0;
|
||||
|
||||
public CountThreadSafe(QueueDao queueDao) {
|
||||
this.queueDao = queueDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
count = queueDao.count();
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetLastPlayedTimestampThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private final String mediaId;
|
||||
|
||||
public SetLastPlayedTimestampThreadSafe(QueueDao queueDao, String mediaId) {
|
||||
this.queueDao = queueDao;
|
||||
this.mediaId = mediaId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
queueDao.setLastPlay(mediaId, System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
private static class SetPlayingPausedTimestampThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private final String mediaId;
|
||||
private final long ms;
|
||||
|
||||
public SetPlayingPausedTimestampThreadSafe(QueueDao queueDao, String mediaId, long ms) {
|
||||
this.queueDao = queueDao;
|
||||
this.mediaId = mediaId;
|
||||
this.ms = ms;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
queueDao.setPlayingChanged(mediaId, ms);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GetLastPlayedMediaThreadSafe implements Runnable {
|
||||
private final QueueDao queueDao;
|
||||
private Queue lastMediaPlayed;
|
||||
|
||||
public GetLastPlayedMediaThreadSafe(QueueDao queueDao) {
|
||||
this.queueDao = queueDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
lastMediaPlayed = queueDao.getLastPlayed();
|
||||
}
|
||||
|
||||
public Queue getQueueItem() {
|
||||
return lastMediaPlayed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class RadioRepository {
|
||||
public MutableLiveData<List<InternetRadioStation>> getInternetRadioStations() {
|
||||
MutableLiveData<List<InternetRadioStation>> radioStation = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.getInternetRadioStations()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getInternetRadioStations() != null && response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations() != null) {
|
||||
radioStation.setValue(response.body().getSubsonicResponse().getInternetRadioStations().getInternetRadioStations());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return radioStation;
|
||||
}
|
||||
|
||||
public void createInternetRadioStation(String name, String streamURL, String homepageURL) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.createInternetRadioStation(streamURL, name, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void updateInternetRadioStation(String id, String name, String streamURL, String homepageURL) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.updateInternetRadioStation(id, streamURL, name, homepageURL)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteInternetRadioStation(String id) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getInternetRadioClient()
|
||||
.deleteInternetRadioStation(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.interfaces.ScanCallback;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
|
||||
public class ScanRepository {
|
||||
public void startScan(ScanCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaLibraryScanningClient()
|
||||
.startScan()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onError(new Exception(t.getMessage()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void getScanStatus(ScanCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaLibraryScanningClient()
|
||||
.startScan()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onError(new Exception(t.getMessage()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
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.RecentSearchDao;
|
||||
import com.cappielloantonio.tempo.model.RecentSearch;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
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.SearchResult2;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SearchResult3;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SearchingRepository {
|
||||
private final RecentSearchDao recentSearchDao = AppDatabase.getInstance().recentSearchDao();
|
||||
|
||||
public MutableLiveData<SearchResult2> search2(String query) {
|
||||
MutableLiveData<SearchResult2> result = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSearchingClient()
|
||||
.search3(query, 20, 20, 20)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult2());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MutableLiveData<SearchResult3> search3(String query) {
|
||||
MutableLiveData<SearchResult3> result = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSearchingClient()
|
||||
.search3(query, 20, 20, 20)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
result.setValue(response.body().getSubsonicResponse().getSearchResult3());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<String>> getSuggestions(String query) {
|
||||
MutableLiveData<List<String>> suggestions = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSearchingClient()
|
||||
.search3(query, 5, 5, 5)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<String> newSuggestions = new ArrayList();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSearchResult3() != null) {
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getArtists() != null) {
|
||||
for (ArtistID3 artistID3 : response.body().getSubsonicResponse().getSearchResult3().getArtists()) {
|
||||
newSuggestions.add(artistID3.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getAlbums() != null) {
|
||||
for (AlbumID3 albumID3 : response.body().getSubsonicResponse().getSearchResult3().getAlbums()) {
|
||||
newSuggestions.add(albumID3.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (response.body().getSubsonicResponse().getSearchResult3().getSongs() != null) {
|
||||
for (Child song : response.body().getSubsonicResponse().getSearchResult3().getSongs()) {
|
||||
newSuggestions.add(song.getTitle());
|
||||
}
|
||||
}
|
||||
|
||||
LinkedHashSet<String> hashSet = new LinkedHashSet<>(newSuggestions);
|
||||
ArrayList<String> suggestionsWithoutDuplicates = new ArrayList<>(hashSet);
|
||||
|
||||
suggestions.setValue(suggestionsWithoutDuplicates);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
public void insert(RecentSearch recentSearch) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(recentSearchDao, recentSearch);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void delete(RecentSearch recentSearch) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(recentSearchDao, recentSearch);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public List<String> getRecentSearchSuggestion() {
|
||||
List<String> recent = new ArrayList<>();
|
||||
|
||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
|
||||
Thread thread = new Thread(suggestionsThread);
|
||||
thread.start();
|
||||
|
||||
try {
|
||||
thread.join();
|
||||
recent = suggestionsThread.getRecent();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return recent;
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final RecentSearchDao recentSearchDao;
|
||||
private final RecentSearch recentSearch;
|
||||
|
||||
public DeleteThreadSafe(RecentSearchDao recentSearchDao, RecentSearch recentSearch) {
|
||||
this.recentSearchDao = recentSearchDao;
|
||||
this.recentSearch = recentSearch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recentSearchDao.delete(recentSearch);
|
||||
}
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final RecentSearchDao recentSearchDao;
|
||||
private final RecentSearch recentSearch;
|
||||
|
||||
public InsertThreadSafe(RecentSearchDao recentSearchDao, RecentSearch recentSearch) {
|
||||
this.recentSearchDao = recentSearchDao;
|
||||
this.recentSearch = recentSearch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recentSearchDao.insert(recentSearch);
|
||||
}
|
||||
}
|
||||
|
||||
private static class RecentThreadSafe implements Runnable {
|
||||
private final RecentSearchDao recentSearchDao;
|
||||
private List<String> recent = new ArrayList<>();
|
||||
|
||||
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
|
||||
this.recentSearchDao = recentSearchDao;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recent = recentSearchDao.getRecent();
|
||||
}
|
||||
|
||||
public List<String> getRecent() {
|
||||
return recent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.lifecycle.LiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.database.AppDatabase;
|
||||
import com.cappielloantonio.tempo.database.dao.ServerDao;
|
||||
import com.cappielloantonio.tempo.model.Server;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ServerRepository {
|
||||
private static final String TAG = "QueueRepository";
|
||||
|
||||
private final ServerDao serverDao = AppDatabase.getInstance().serverDao();
|
||||
|
||||
public LiveData<List<Server>> getLiveServer() {
|
||||
return serverDao.getAll();
|
||||
}
|
||||
|
||||
public void insert(Server server) {
|
||||
InsertThreadSafe insert = new InsertThreadSafe(serverDao, server);
|
||||
Thread thread = new Thread(insert);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void delete(Server server) {
|
||||
DeleteThreadSafe delete = new DeleteThreadSafe(serverDao, server);
|
||||
Thread thread = new Thread(delete);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
private static class InsertThreadSafe implements Runnable {
|
||||
private final ServerDao serverDao;
|
||||
private final Server server;
|
||||
|
||||
public InsertThreadSafe(ServerDao serverDao, Server server) {
|
||||
this.serverDao = serverDao;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
serverDao.insert(server);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DeleteThreadSafe implements Runnable {
|
||||
private final ServerDao serverDao;
|
||||
private final Server server;
|
||||
|
||||
public DeleteThreadSafe(ServerDao serverDao, Server server) {
|
||||
this.serverDao = serverDao;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
serverDao.delete(server);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Share;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SharingRepository {
|
||||
public MutableLiveData<List<Share>> getShares() {
|
||||
MutableLiveData<List<Share>> shares = new MutableLiveData<>(new ArrayList<>());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.getShares()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null) {
|
||||
shares.setValue(response.body().getSubsonicResponse().getShares().getShares());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return shares;
|
||||
}
|
||||
|
||||
public MutableLiveData<Share> createShare(String id, String description, Long expires) {
|
||||
MutableLiveData<Share> share = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.createShare(id, description, expires)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getShares() != null && response.body().getSubsonicResponse().getShares().getShares() != null && response.body().getSubsonicResponse().getShares().getShares().get(0) != null) {
|
||||
share.setValue(response.body().getSubsonicResponse().getShares().getShares().get(0));
|
||||
} else {
|
||||
share.setValue(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
share.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return share;
|
||||
}
|
||||
|
||||
public void updateShare(String id, String description, Long expires) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.updateShare(id, description, expires)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteShare(String id) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSharingClient()
|
||||
.deleteShare(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,260 @@
|
|||
package com.cappielloantonio.tempo.repository;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SongRepository {
|
||||
private static final String TAG = "SongRepository";
|
||||
|
||||
public MutableLiveData<List<Child>> getStarredSongs(boolean random, int size) {
|
||||
MutableLiveData<List<Child>> starredSongs = new MutableLiveData<>(Collections.emptyList());
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getStarred2()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getStarred2() != null) {
|
||||
List<Child> songs = response.body().getSubsonicResponse().getStarred2().getSongs();
|
||||
|
||||
if (songs != null) {
|
||||
if (!random) {
|
||||
starredSongs.setValue(songs);
|
||||
} else {
|
||||
Collections.shuffle(songs);
|
||||
starredSongs.setValue(songs.subList(0, Math.min(size, songs.size())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return starredSongs;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getInstantMix(String id, int count) {
|
||||
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSimilarSongs2(id, count)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSimilarSongs2() != null) {
|
||||
instantMix.setValue(response.body().getSubsonicResponse().getSimilarSongs2().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
instantMix.setValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return instantMix;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSample(int number, Integer fromYear, Integer toYear) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(number, fromYear, toYear)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getRandomSampleWithGenre(int number, Integer fromYear, Integer toYear, String genre) {
|
||||
MutableLiveData<List<Child>> randomSongsSample = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getRandomSongs(number, fromYear, toYear, genre)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
randomSongsSample.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return randomSongsSample;
|
||||
}
|
||||
|
||||
public void scrobble(String id, boolean submission) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.scrobble(id, submission)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setRating(String id, int rating) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaAnnotationClient()
|
||||
.setRating(id, rating)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getSongsByGenre(String id, int page) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 100, 100 * page)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songsByGenre.setValue(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<Child>> getSongsByGenres(ArrayList<String> genresId) {
|
||||
MutableLiveData<List<Child>> songsByGenre = new MutableLiveData<>();
|
||||
|
||||
for (String id : genresId)
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getAlbumSongListClient()
|
||||
.getSongsByGenre(id, 500, 0)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getSongsByGenre() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getSongsByGenre().getSongs());
|
||||
}
|
||||
|
||||
songsByGenre.setValue(songs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return songsByGenre;
|
||||
}
|
||||
|
||||
public MutableLiveData<Child> getSong(String id) {
|
||||
MutableLiveData<Child> song = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getBrowsingClient()
|
||||
.getSong(id)
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
song.setValue(response.body().getSubsonicResponse().getSong());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return song;
|
||||
}
|
||||
|
||||
public MutableLiveData<String> getSongLyrics(Child song) {
|
||||
MutableLiveData<String> lyrics = new MutableLiveData<>(null);
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getMediaRetrievalClient()
|
||||
.getLyrics(song.getArtist(), song.getTitle())
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getLyrics() != null) {
|
||||
lyrics.setValue(response.body().getSubsonicResponse().getLyrics().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
return lyrics;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
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;
|
||||
import com.cappielloantonio.tempo.subsonic.models.ResponseStatus;
|
||||
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.Callback;
|
||||
import retrofit2.Response;
|
||||
|
||||
public class SystemRepository {
|
||||
public void checkUserCredential(SystemCallback callback) {
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSystemClient()
|
||||
.ping()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.body() != null) {
|
||||
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
||||
String password = response.raw().request().url().queryParameter("p");
|
||||
String token = response.raw().request().url().queryParameter("t");
|
||||
String salt = response.raw().request().url().queryParameter("s");
|
||||
callback.onSuccess(password, token, salt);
|
||||
} else {
|
||||
callback.onError(new Exception("Empty response"));
|
||||
}
|
||||
} else {
|
||||
callback.onError(new Exception(String.valueOf(response.code())));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
callback.onError(new Exception(t.getMessage()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public MutableLiveData<SubsonicResponse> ping() {
|
||||
MutableLiveData<SubsonicResponse> pingResult = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSystemClient()
|
||||
.ping()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
pingResult.postValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return pingResult;
|
||||
}
|
||||
|
||||
public MutableLiveData<List<OpenSubsonicExtension>> getOpenSubsonicExtensions() {
|
||||
MutableLiveData<List<OpenSubsonicExtension>> extensionsResult = new MutableLiveData<>();
|
||||
|
||||
App.getSubsonicClientInstance(false)
|
||||
.getSystemClient()
|
||||
.getOpenSubsonicExtensions()
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null) {
|
||||
extensionsResult.postValue(response.body().getSubsonicResponse().getOpenSubsonicExtensions());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
|
||||
extensionsResult.postValue(null);
|
||||
}
|
||||
});
|
||||
|
||||
return extensionsResult;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.exoplayer.offline.Download;
|
||||
import androidx.media3.exoplayer.offline.DownloadCursor;
|
||||
import androidx.media3.exoplayer.offline.DownloadHelper;
|
||||
import androidx.media3.exoplayer.offline.DownloadIndex;
|
||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||
import androidx.media3.exoplayer.offline.DownloadRequest;
|
||||
import androidx.media3.exoplayer.offline.DownloadService;
|
||||
|
||||
import com.cappielloantonio.tempo.repository.DownloadRepository;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class DownloaderManager {
|
||||
private static final String TAG = "DownloaderManager";
|
||||
|
||||
private final Context context;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final DownloadIndex downloadIndex;
|
||||
|
||||
private static HashMap<String, Download> downloads;
|
||||
|
||||
public DownloaderManager(Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
|
||||
downloads = new HashMap<>();
|
||||
downloadIndex = downloadManager.getDownloadIndex();
|
||||
|
||||
loadDownloads();
|
||||
}
|
||||
|
||||
private DownloadRequest buildDownloadRequest(MediaItem mediaItem) {
|
||||
return DownloadHelper
|
||||
.forMediaItem(
|
||||
context,
|
||||
mediaItem,
|
||||
DownloadUtil.buildRenderersFactory(context, false),
|
||||
dataSourceFactory)
|
||||
.getDownloadRequest(Util.getUtf8Bytes(checkNotNull(mediaItem.mediaId)))
|
||||
.copyWithId(mediaItem.mediaId);
|
||||
}
|
||||
|
||||
public boolean isDownloaded(String mediaId) {
|
||||
@Nullable Download download = downloads.get(mediaId);
|
||||
return download != null && download.state != Download.STATE_FAILED;
|
||||
}
|
||||
|
||||
public boolean isDownloaded(MediaItem mediaItem) {
|
||||
return isDownloaded(mediaItem.mediaId);
|
||||
}
|
||||
|
||||
public boolean areDownloaded(List<MediaItem> mediaItems) {
|
||||
return mediaItems.stream().anyMatch(this::isDownloaded);
|
||||
}
|
||||
|
||||
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||
download.setDownloadUri(mediaItem.requestMetadata.mediaUri.toString());
|
||||
|
||||
DownloadService.sendAddDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem), false);
|
||||
insertDatabase(download);
|
||||
}
|
||||
|
||||
public void download(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||
for (int counter = 0; counter < mediaItems.size(); counter++) {
|
||||
download(mediaItems.get(counter), downloads.get(counter));
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
|
||||
DownloadService.sendRemoveDownload(context, DownloaderService.class, buildDownloadRequest(mediaItem).id, false);
|
||||
deleteDatabase(download.getId());
|
||||
downloads.remove(download.getId());
|
||||
}
|
||||
|
||||
public void remove(List<MediaItem> mediaItems, List<com.cappielloantonio.tempo.model.Download> downloads) {
|
||||
for (int counter = 0; counter < mediaItems.size(); counter++) {
|
||||
remove(mediaItems.get(counter), downloads.get(counter));
|
||||
}
|
||||
}
|
||||
|
||||
public void removeAll() {
|
||||
DownloadService.sendRemoveAllDownloads(context, DownloaderService.class, false);
|
||||
deleteAllDatabase();
|
||||
DownloadUtil.eraseDownloadFolder(context);
|
||||
}
|
||||
|
||||
private void loadDownloads() {
|
||||
try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) {
|
||||
while (loadedDownloads.moveToNext()) {
|
||||
Download download = loadedDownloads.getDownload();
|
||||
downloads.put(download.request.id, download);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to query downloads", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getDownloadNotificationMessage(String id) {
|
||||
com.cappielloantonio.tempo.model.Download download = getDownloadRepository().getDownload(id);
|
||||
return download != null ? download.getTitle() : null;
|
||||
}
|
||||
|
||||
public static void updateRequestDownload(Download download) {
|
||||
updateDatabase(download.request.id);
|
||||
downloads.put(download.request.id, download);
|
||||
}
|
||||
|
||||
public static void removeRequestDownload(Download download) {
|
||||
deleteDatabase(download.request.id);
|
||||
downloads.remove(download.request.id);
|
||||
}
|
||||
|
||||
private static DownloadRepository getDownloadRepository() {
|
||||
return new DownloadRepository();
|
||||
}
|
||||
|
||||
private static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
|
||||
getDownloadRepository().insert(download);
|
||||
}
|
||||
|
||||
private static void deleteDatabase(String id) {
|
||||
getDownloadRepository().delete(id);
|
||||
}
|
||||
|
||||
private static void deleteAllDatabase() {
|
||||
getDownloadRepository().deleteAll();
|
||||
}
|
||||
|
||||
private static void updateDatabase(String id) {
|
||||
getDownloadRepository().update(id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.media3.common.util.NotificationUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.offline.Download;
|
||||
import androidx.media3.exoplayer.offline.DownloadManager;
|
||||
import androidx.media3.exoplayer.offline.DownloadNotificationHelper;
|
||||
import androidx.media3.exoplayer.scheduler.PlatformScheduler;
|
||||
import androidx.media3.exoplayer.scheduler.Requirements;
|
||||
import androidx.media3.exoplayer.scheduler.Scheduler;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.util.DownloadUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@UnstableApi
|
||||
public class DownloaderService extends androidx.media3.exoplayer.offline.DownloadService {
|
||||
|
||||
private static final int JOB_ID = 1;
|
||||
private static final int FOREGROUND_NOTIFICATION_ID = 1;
|
||||
|
||||
public DownloaderService() {
|
||||
super(FOREGROUND_NOTIFICATION_ID, DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL, DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID, R.string.exo_download_notification_channel_name, 0);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected DownloadManager getDownloadManager() {
|
||||
DownloadManager downloadManager = DownloadUtil.getDownloadManager(this);
|
||||
DownloadNotificationHelper downloadNotificationHelper = DownloadUtil.getDownloadNotificationHelper(this);
|
||||
downloadManager.addListener(new TerminalStateNotificationHelper(this, downloadNotificationHelper, FOREGROUND_NOTIFICATION_ID + 1));
|
||||
return downloadManager;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Scheduler getScheduler() {
|
||||
return new PlatformScheduler(this, JOB_ID);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
protected Notification getForegroundNotification(@NonNull List<Download> downloads, @Requirements.RequirementFlags int notMetRequirements) {
|
||||
return DownloadUtil.getDownloadNotificationHelper(this).buildProgressNotification(this, R.drawable.ic_download, null, null, downloads, notMetRequirements);
|
||||
}
|
||||
|
||||
private static final class TerminalStateNotificationHelper implements DownloadManager.Listener {
|
||||
private final Context context;
|
||||
private final DownloadNotificationHelper notificationHelper;
|
||||
|
||||
private final Notification successfulDownloadGroupNotification;
|
||||
private final Notification failedDownloadGroupNotification;
|
||||
|
||||
private final int successfulDownloadGroupNotificationId;
|
||||
private final int failedDownloadGroupNotificationId;
|
||||
|
||||
private int nextNotificationId;
|
||||
|
||||
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
|
||||
this.context = context.getApplicationContext();
|
||||
this.notificationHelper = notificationHelper;
|
||||
nextNotificationId = firstNotificationId;
|
||||
|
||||
successfulDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
|
||||
this.context,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP,
|
||||
R.drawable.ic_check_circle,
|
||||
"Downloads completed"
|
||||
);
|
||||
|
||||
failedDownloadGroupNotification = DownloadUtil.buildGroupSummaryNotification(
|
||||
this.context,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_CHANNEL_ID,
|
||||
DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP,
|
||||
R.drawable.ic_error,
|
||||
"Downloads failed"
|
||||
);
|
||||
|
||||
successfulDownloadGroupNotificationId = nextNotificationId++;
|
||||
failedDownloadGroupNotificationId = nextNotificationId++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadChanged(@NonNull DownloadManager downloadManager, Download download, @Nullable Exception finalException) {
|
||||
Notification notification;
|
||||
|
||||
if (download.state == Download.STATE_COMPLETED) {
|
||||
notification = notificationHelper.buildDownloadCompletedNotification(context, R.drawable.ic_check_circle, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_SUCCESSFUL_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, successfulDownloadGroupNotificationId, successfulDownloadGroupNotification);
|
||||
DownloaderManager.updateRequestDownload(download);
|
||||
} else if (download.state == Download.STATE_FAILED) {
|
||||
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, DownloaderManager.getDownloadNotificationMessage(download.request.id));
|
||||
notification = Notification.Builder.recoverBuilder(context, notification).setGroup(DownloadUtil.DOWNLOAD_NOTIFICATION_FAILED_GROUP).build();
|
||||
NotificationUtil.setNotification(this.context, failedDownloadGroupNotificationId, failedDownloadGroupNotification);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationUtil.setNotification(context, nextNotificationId++, notification);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownloadRemoved(@NonNull DownloadManager downloadManager, Download download) {
|
||||
DownloaderManager.removeRequestDownload(download);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.cappielloantonio.tempo.service
|
||||
|
||||
import android.media.audiofx.Equalizer
|
||||
|
||||
class EqualizerManager {
|
||||
|
||||
private var equalizer: Equalizer? = null
|
||||
|
||||
fun attachToSession(audioSessionId: Int): Boolean {
|
||||
release()
|
||||
if (audioSessionId != 0 && audioSessionId != -1) {
|
||||
try {
|
||||
equalizer = Equalizer(0, audioSessionId).apply {
|
||||
enabled = true
|
||||
}
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
// Some devices may not support Equalizer or audio session may be invalid
|
||||
equalizer = null
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun setBandLevel(band: Short, level: Short) {
|
||||
equalizer?.setBandLevel(band, level)
|
||||
}
|
||||
|
||||
fun getNumberOfBands(): Short = equalizer?.numberOfBands ?: 0
|
||||
|
||||
fun getBandLevelRange(): ShortArray? = equalizer?.bandLevelRange
|
||||
|
||||
fun getCenterFreq(band: Short): Int? =
|
||||
equalizer?.getCenterFreq(band)?.div(1000)
|
||||
|
||||
fun getBandLevel(band: Short): Short? =
|
||||
equalizer?.getBandLevel(band)
|
||||
|
||||
fun setEnabled(enabled: Boolean) {
|
||||
equalizer?.enabled = enabled
|
||||
}
|
||||
|
||||
fun release() {
|
||||
equalizer?.release()
|
||||
equalizer = null
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,485 @@
|
|||
package com.cappielloantonio.tempo.service;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
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;
|
||||
import com.cappielloantonio.tempo.repository.QueueRepository;
|
||||
import com.cappielloantonio.tempo.repository.SongRepository;
|
||||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.cappielloantonio.tempo.util.MappingUtil;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.viewmodel.PlaybackViewModel;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class MediaManager {
|
||||
private static final String TAG = "MediaManager";
|
||||
private static WeakReference<MediaBrowser> attachedBrowserRef = new WeakReference<>(null);
|
||||
|
||||
public static void registerPlaybackObserver(
|
||||
ListenableFuture<MediaBrowser> browserFuture,
|
||||
PlaybackViewModel playbackViewModel
|
||||
) {
|
||||
if (browserFuture == null) return;
|
||||
|
||||
Futures.addCallback(browserFuture, new FutureCallback<MediaBrowser>() {
|
||||
@Override
|
||||
public void onSuccess(MediaBrowser browser) {
|
||||
MediaBrowser current = attachedBrowserRef.get();
|
||||
if (current != browser) {
|
||||
browser.addListener(new Player.Listener() {
|
||||
@Override
|
||||
public void onEvents(@NonNull Player player, @NonNull Player.Events events) {
|
||||
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION)
|
||||
|| events.contains(Player.EVENT_PLAY_WHEN_READY_CHANGED)
|
||||
|| events.contains(Player.EVENT_PLAYBACK_STATE_CHANGED)) {
|
||||
|
||||
String mediaId = player.getCurrentMediaItem() != null
|
||||
? player.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
|
||||
boolean playing = player.getPlaybackState() == Player.STATE_READY
|
||||
&& player.getPlayWhenReady();
|
||||
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
String mediaId = browser.getCurrentMediaItem() != null
|
||||
? browser.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady();
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
|
||||
attachedBrowserRef = new WeakReference<>(browser);
|
||||
} else {
|
||||
String mediaId = browser.getCurrentMediaItem() != null
|
||||
? browser.getCurrentMediaItem().mediaId
|
||||
: null;
|
||||
boolean playing = browser.getPlaybackState() == Player.STATE_READY && browser.getPlayWhenReady();
|
||||
playbackViewModel.update(mediaId, playing);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Throwable t) {
|
||||
Log.e(TAG, "Failed to get MediaBrowser instance", t);
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
public static void onBrowserReleased(@Nullable MediaBrowser released) {
|
||||
MediaBrowser attached = attachedBrowserRef.get();
|
||||
if (attached == released) {
|
||||
attachedBrowserRef.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (mediaBrowserListenableFuture.get().isPlaying()) {
|
||||
mediaBrowserListenableFuture.get().pause();
|
||||
}
|
||||
|
||||
mediaBrowserListenableFuture.get().stop();
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
clearDatabase();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void hide(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (mediaBrowserListenableFuture.get().isPlaying()) {
|
||||
mediaBrowserListenableFuture.get().pause();
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void check(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (mediaBrowserListenableFuture.get().getMediaItemCount() < 1) {
|
||||
List<Child> media = getQueueRepository().getMedia();
|
||||
if (media != null && media.size() >= 1) {
|
||||
init(mediaBrowserListenableFuture, media);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void init(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(media));
|
||||
mediaBrowserListenableFuture.get().seekTo(getQueueRepository().getLastPlayedMediaIndex(), getQueueRepository().getLastPlayedMediaTimestamp());
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser browser = mediaBrowserListenableFuture.get();
|
||||
browser.clearMediaItems();
|
||||
browser.setMediaItems(MappingUtil.mapMediaItems(media));
|
||||
browser.prepare();
|
||||
|
||||
Player.Listener timelineListener = new Player.Listener() {
|
||||
@Override
|
||||
public void onTimelineChanged(Timeline timeline, int reason) {
|
||||
int itemCount = browser.getMediaItemCount();
|
||||
if (itemCount > 0 && startIndex >= 0 && startIndex < itemCount) {
|
||||
browser.seekTo(startIndex, 0);
|
||||
browser.play();
|
||||
browser.removeListener(this);
|
||||
}
|
||||
}
|
||||
};
|
||||
browser.addListener(timelineListener);
|
||||
|
||||
enqueueDatabase(media, true, 0);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void startQueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(media));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
enqueueDatabase(media, true, 0);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void playDownloadedMediaItem(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, MediaItem mediaItem) {
|
||||
if (mediaBrowserListenableFuture != null && mediaItem != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
MediaBrowser mediaBrowser = mediaBrowserListenableFuture.get();
|
||||
mediaBrowser.clearMediaItems();
|
||||
mediaBrowser.setMediaItem(mediaItem);
|
||||
mediaBrowser.prepare();
|
||||
mediaBrowser.play();
|
||||
clearDatabase();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void startRadio(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, InternetRadioStation internetRadioStation) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapInternetRadioStation(internetRadioStation));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void startPodcast(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, PodcastEpisode podcastEpisode) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().clearMediaItems();
|
||||
mediaBrowserListenableFuture.get().setMediaItem(MappingUtil.mapMediaItem(podcastEpisode));
|
||||
mediaBrowserListenableFuture.get().prepare();
|
||||
mediaBrowserListenableFuture.get().play();
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void enqueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, boolean playImmediatelyAfter) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
|
||||
mediaBrowserListenableFuture.get().addMediaItems(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItems(media));
|
||||
} else {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getMediaItemCount());
|
||||
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media));
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void enqueue(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, Child media, boolean playImmediatelyAfter) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (playImmediatelyAfter && mediaBrowserListenableFuture.get().getNextMediaItemIndex() != -1) {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getNextMediaItemIndex());
|
||||
mediaBrowserListenableFuture.get().addMediaItem(mediaBrowserListenableFuture.get().getNextMediaItemIndex(), MappingUtil.mapMediaItem(media));
|
||||
} else {
|
||||
enqueueDatabase(media, false, mediaBrowserListenableFuture.get().getMediaItemCount());
|
||||
mediaBrowserListenableFuture.get().addMediaItem(MappingUtil.mapMediaItem(media));
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void shuffle(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int startIndex, int endIndex) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(startIndex, endIndex + 1);
|
||||
mediaBrowserListenableFuture.get().addMediaItems(MappingUtil.mapMediaItems(media).subList(startIndex, endIndex + 1));
|
||||
swapDatabase(media);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void swap(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int from, int to) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().moveMediaItem(from, to);
|
||||
swapDatabase(media);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void remove(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int toRemove) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
if (mediaBrowserListenableFuture.get().getMediaItemCount() > 1 && mediaBrowserListenableFuture.get().getCurrentMediaItemIndex() != toRemove) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItem(toRemove);
|
||||
removeDatabase(media, toRemove);
|
||||
} else {
|
||||
removeDatabase(media, -1);
|
||||
}
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeRange(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, List<Child> media, int fromItem, int toItem) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
mediaBrowserListenableFuture.get().removeMediaItems(fromItem, toItem);
|
||||
removeRangeDatabase(media, fromItem, toItem);
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void getCurrentIndex(ListenableFuture<MediaBrowser> mediaBrowserListenableFuture, MediaIndexCallback callback) {
|
||||
if (mediaBrowserListenableFuture != null) {
|
||||
mediaBrowserListenableFuture.addListener(() -> {
|
||||
try {
|
||||
if (mediaBrowserListenableFuture.isDone()) {
|
||||
callback.onRecovery(mediaBrowserListenableFuture.get().getCurrentMediaItemIndex());
|
||||
}
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}, MoreExecutors.directExecutor());
|
||||
}
|
||||
}
|
||||
|
||||
public static void setLastPlayedTimestamp(MediaItem mediaItem) {
|
||||
if (mediaItem != null) getQueueRepository().setLastPlayedTimestamp(mediaItem.mediaId);
|
||||
}
|
||||
|
||||
public static void setPlayingPausedTimestamp(MediaItem mediaItem, long ms) {
|
||||
if (mediaItem != null)
|
||||
getQueueRepository().setPlayingPausedTimestamp(mediaItem.mediaId, ms);
|
||||
}
|
||||
|
||||
public static void scrobble(MediaItem mediaItem, boolean submission) {
|
||||
if (mediaItem != null && Preferences.isScrobblingEnabled()) {
|
||||
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"), submission);
|
||||
}
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
}
|
||||
|
||||
private static QueueRepository getQueueRepository() {
|
||||
return new QueueRepository();
|
||||
}
|
||||
|
||||
private static SongRepository getSongRepository() {
|
||||
return new SongRepository();
|
||||
}
|
||||
|
||||
private static ChronologyRepository getChronologyRepository() {
|
||||
return new ChronologyRepository();
|
||||
}
|
||||
|
||||
private static void enqueueDatabase(List<Child> media, boolean reset, int afterIndex) {
|
||||
getQueueRepository().insertAll(media, reset, afterIndex);
|
||||
}
|
||||
|
||||
private static void enqueueDatabase(Child media, boolean reset, int afterIndex) {
|
||||
getQueueRepository().insert(media, reset, afterIndex);
|
||||
}
|
||||
|
||||
private static void swapDatabase(List<Child> media) {
|
||||
getQueueRepository().insertAll(media, true, 0);
|
||||
}
|
||||
|
||||
private static void removeDatabase(List<Child> media, int toRemove) {
|
||||
if (toRemove != -1) {
|
||||
media.remove(toRemove);
|
||||
getQueueRepository().insertAll(media, true, 0);
|
||||
}
|
||||
}
|
||||
|
||||
private static void removeRangeDatabase(List<Child> media, int fromItem, int toItem) {
|
||||
List<Child> toRemove = media.subList(fromItem, toItem);
|
||||
|
||||
media.removeAll(toRemove);
|
||||
|
||||
getQueueRepository().insertAll(media, true, 0);
|
||||
}
|
||||
|
||||
public static void clearDatabase() {
|
||||
getQueueRepository().deleteAll();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
package com.cappielloantonio.tempo.subsonic
|
||||
|
||||
import com.cappielloantonio.tempo.App
|
||||
import com.cappielloantonio.tempo.subsonic.utils.CacheUtil
|
||||
import com.cappielloantonio.tempo.subsonic.utils.EmptyDateTypeAdapter
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.Cache
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class RetrofitClient(subsonic: Subsonic) {
|
||||
var retrofit: Retrofit
|
||||
|
||||
init {
|
||||
val gson = GsonBuilder()
|
||||
.registerTypeAdapter(Date::class.java, EmptyDateTypeAdapter())
|
||||
.setLenient()
|
||||
.create()
|
||||
|
||||
retrofit = Retrofit.Builder()
|
||||
.baseUrl(subsonic.url)
|
||||
.addConverterFactory(GsonConverterFactory.create(gson))
|
||||
.client(getOkHttpClient())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getOkHttpClient(): OkHttpClient {
|
||||
val cacheUtil = CacheUtil(60, 60 * 60 * 24 * 30)
|
||||
|
||||
// BrowsingClient 60
|
||||
// MediaAnnotationClient 0
|
||||
// MediaLibraryScanningClient 0
|
||||
// MediaRetrievalClient 0
|
||||
// PlaylistClient 0
|
||||
// PodcastClient 60
|
||||
// SearchClient 60
|
||||
// SystemClient 60
|
||||
// AlbumSongListClient 60
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.callTimeout(2, TimeUnit.MINUTES)
|
||||
.connectTimeout(20, TimeUnit.SECONDS)
|
||||
.readTimeout(30, TimeUnit.SECONDS)
|
||||
.writeTimeout(30, TimeUnit.SECONDS)
|
||||
.addInterceptor(getHttpLoggingInterceptor())
|
||||
.addInterceptor(cacheUtil.offlineInterceptor)
|
||||
// .addNetworkInterceptor(cacheUtil.onlineInterceptor)
|
||||
.cache(getCache())
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getHttpLoggingInterceptor(): HttpLoggingInterceptor {
|
||||
val loggingInterceptor = HttpLoggingInterceptor()
|
||||
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
|
||||
return loggingInterceptor
|
||||
}
|
||||
|
||||
private fun getCache(): Cache {
|
||||
val cacheSize = 10 * 1024 * 1024
|
||||
return Cache(App.getContext().cacheDir, cacheSize.toLong())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package com.cappielloantonio.tempo.subsonic;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.api.albumsonglist.AlbumSongListClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.bookmarks.BookmarksClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.browsing.BrowsingClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.internetradio.InternetRadioClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.mediaannotation.MediaAnnotationClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.medialibraryscanning.MediaLibraryScanningClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.mediaretrieval.MediaRetrievalClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.open.OpenClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.playlist.PlaylistClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.sharing.SharingClient;
|
||||
import com.cappielloantonio.tempo.subsonic.api.system.SystemClient;
|
||||
import com.cappielloantonio.tempo.subsonic.base.Version;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Subsonic {
|
||||
private static final Version API_MAX_VERSION = Version.of("1.15.0");
|
||||
|
||||
private final Version apiVersion = API_MAX_VERSION;
|
||||
private final SubsonicPreferences preferences;
|
||||
|
||||
private SystemClient systemClient;
|
||||
private BrowsingClient browsingClient;
|
||||
private MediaRetrievalClient mediaRetrievalClient;
|
||||
private PlaylistClient playlistClient;
|
||||
private SearchingClient searchingClient;
|
||||
private AlbumSongListClient albumSongListClient;
|
||||
private MediaAnnotationClient mediaAnnotationClient;
|
||||
private PodcastClient podcastClient;
|
||||
private MediaLibraryScanningClient mediaLibraryScanningClient;
|
||||
private BookmarksClient bookmarksClient;
|
||||
private InternetRadioClient internetRadioClient;
|
||||
private SharingClient sharingClient;
|
||||
private OpenClient openClient;
|
||||
|
||||
public Subsonic(SubsonicPreferences preferences) {
|
||||
this.preferences = preferences;
|
||||
}
|
||||
|
||||
public Version getApiVersion() {
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
public SystemClient getSystemClient() {
|
||||
if (systemClient == null) {
|
||||
systemClient = new SystemClient(this);
|
||||
}
|
||||
return systemClient;
|
||||
}
|
||||
|
||||
public BrowsingClient getBrowsingClient() {
|
||||
if (browsingClient == null) {
|
||||
browsingClient = new BrowsingClient(this);
|
||||
}
|
||||
return browsingClient;
|
||||
}
|
||||
|
||||
public MediaRetrievalClient getMediaRetrievalClient() {
|
||||
if (mediaRetrievalClient == null) {
|
||||
mediaRetrievalClient = new MediaRetrievalClient(this);
|
||||
}
|
||||
return mediaRetrievalClient;
|
||||
}
|
||||
|
||||
public PlaylistClient getPlaylistClient() {
|
||||
if (playlistClient == null) {
|
||||
playlistClient = new PlaylistClient(this);
|
||||
}
|
||||
return playlistClient;
|
||||
}
|
||||
|
||||
public SearchingClient getSearchingClient() {
|
||||
if (searchingClient == null) {
|
||||
searchingClient = new SearchingClient(this);
|
||||
}
|
||||
return searchingClient;
|
||||
}
|
||||
|
||||
public AlbumSongListClient getAlbumSongListClient() {
|
||||
if (albumSongListClient == null) {
|
||||
albumSongListClient = new AlbumSongListClient(this);
|
||||
}
|
||||
return albumSongListClient;
|
||||
}
|
||||
|
||||
public MediaAnnotationClient getMediaAnnotationClient() {
|
||||
if (mediaAnnotationClient == null) {
|
||||
mediaAnnotationClient = new MediaAnnotationClient(this);
|
||||
}
|
||||
return mediaAnnotationClient;
|
||||
}
|
||||
|
||||
public PodcastClient getPodcastClient() {
|
||||
if (podcastClient == null) {
|
||||
podcastClient = new PodcastClient(this);
|
||||
}
|
||||
return podcastClient;
|
||||
}
|
||||
|
||||
public MediaLibraryScanningClient getMediaLibraryScanningClient() {
|
||||
if (mediaLibraryScanningClient == null) {
|
||||
mediaLibraryScanningClient = new MediaLibraryScanningClient(this);
|
||||
}
|
||||
return mediaLibraryScanningClient;
|
||||
}
|
||||
|
||||
public BookmarksClient getBookmarksClient() {
|
||||
if (bookmarksClient == null) {
|
||||
bookmarksClient = new BookmarksClient(this);
|
||||
}
|
||||
return bookmarksClient;
|
||||
}
|
||||
|
||||
public InternetRadioClient getInternetRadioClient() {
|
||||
if (internetRadioClient == null) {
|
||||
internetRadioClient = new InternetRadioClient(this);
|
||||
}
|
||||
return internetRadioClient;
|
||||
}
|
||||
|
||||
public SharingClient getSharingClient() {
|
||||
if (sharingClient == null) {
|
||||
sharingClient = new SharingClient(this);
|
||||
}
|
||||
return sharingClient;
|
||||
}
|
||||
|
||||
public OpenClient getOpenClient() {
|
||||
if (openClient == null) {
|
||||
openClient = new OpenClient(this);
|
||||
}
|
||||
return openClient;
|
||||
}
|
||||
|
||||
public String getUrl() {
|
||||
String url = preferences.getServerUrl() + "/rest/";
|
||||
return url.replace("//rest", "/rest");
|
||||
}
|
||||
|
||||
public Map<String, String> getParams() {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("u", preferences.getUsername());
|
||||
|
||||
if (preferences.getAuthentication().getPassword() != null)
|
||||
params.put("p", preferences.getAuthentication().getPassword());
|
||||
if (preferences.getAuthentication().getSalt() != null)
|
||||
params.put("s", preferences.getAuthentication().getSalt());
|
||||
if (preferences.getAuthentication().getToken() != null)
|
||||
params.put("t", preferences.getAuthentication().getToken());
|
||||
|
||||
params.put("v", getApiVersion().getVersionString());
|
||||
params.put("c", preferences.getClientName());
|
||||
params.put("f", "json");
|
||||
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package com.cappielloantonio.tempo.subsonic;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.utils.StringUtil;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class SubsonicPreferences {
|
||||
private String serverUrl;
|
||||
private String username;
|
||||
private String clientName = "Tempus";
|
||||
private SubsonicAuthentication authentication;
|
||||
|
||||
public String getServerUrl() {
|
||||
return serverUrl;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getClientName() {
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public SubsonicAuthentication getAuthentication() {
|
||||
return authentication;
|
||||
}
|
||||
|
||||
public void setServerUrl(String serverUrl) {
|
||||
this.serverUrl = serverUrl;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void setClientName(String clientName) {
|
||||
this.clientName = clientName;
|
||||
}
|
||||
|
||||
public void setAuthentication(String password, String token, String salt, boolean isLowSecurity) {
|
||||
if (password != null) {
|
||||
this.authentication = new SubsonicAuthentication(password, isLowSecurity);
|
||||
}
|
||||
|
||||
if (token != null && salt != null) {
|
||||
this.authentication = new SubsonicAuthentication(token, salt);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SubsonicAuthentication {
|
||||
private String password;
|
||||
private String salt;
|
||||
private String token;
|
||||
|
||||
public SubsonicAuthentication(String password, boolean isLowSecurity) {
|
||||
if (isLowSecurity) {
|
||||
this.password = password;
|
||||
} else {
|
||||
update(password);
|
||||
}
|
||||
}
|
||||
|
||||
public SubsonicAuthentication(String token, String salt) {
|
||||
this.token = token;
|
||||
this.salt = salt;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getSalt() {
|
||||
return salt;
|
||||
}
|
||||
|
||||
public String getToken() {
|
||||
return token;
|
||||
}
|
||||
|
||||
void update(String password) {
|
||||
this.salt = UUID.randomUUID().toString();
|
||||
this.token = StringUtil.tokenize(password + salt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.albumsonglist;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class AlbumSongListClient {
|
||||
private static final String TAG = "BrowsingClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final AlbumSongListService albumSongListService;
|
||||
|
||||
public AlbumSongListClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.albumSongListService = new RetrofitClient(subsonic).getRetrofit().create(AlbumSongListService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getAlbumList(String type, int size, int offset) {
|
||||
Log.d(TAG, "getAlbumList()");
|
||||
return albumSongListService.getAlbumList(subsonic.getParams(), type, size, offset);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getAlbumList2(String type, int size, int offset, Integer fromYear, Integer toYear) {
|
||||
Log.d(TAG, "getAlbumList2()");
|
||||
return albumSongListService.getAlbumList2(subsonic.getParams(), type, size, offset, fromYear, toYear);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getRandomSongs(int size, Integer fromYear, Integer toYear) {
|
||||
Log.d(TAG, "getRandomSongs()");
|
||||
return albumSongListService.getRandomSongs(subsonic.getParams(), size, fromYear, toYear);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getRandomSongs(int size, Integer fromYear, Integer toYear, String genre) {
|
||||
Log.d(TAG, "getRandomSongs()");
|
||||
return albumSongListService.getRandomSongs(subsonic.getParams(), size, fromYear, toYear, genre);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getSongsByGenre(String genre, int count, int offset) {
|
||||
Log.d(TAG, "getSongsByGenre()");
|
||||
return albumSongListService.getSongsByGenre(subsonic.getParams(), genre, count, offset);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getNowPlaying() {
|
||||
Log.d(TAG, "getNowPlaying()");
|
||||
return albumSongListService.getNowPlaying(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getStarred() {
|
||||
Log.d(TAG, "getStarred()");
|
||||
return albumSongListService.getStarred(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getStarred2() {
|
||||
Log.d(TAG, "getStarred2()");
|
||||
return albumSongListService.getStarred2(subsonic.getParams());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.albumsonglist;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface AlbumSongListService {
|
||||
@GET("getAlbumList")
|
||||
Call<ApiResponse> getAlbumList(@QueryMap Map<String, String> params, @Query("type") String type, @Query("size") int size, @Query("offset") int offset);
|
||||
|
||||
@GET("getAlbumList2")
|
||||
Call<ApiResponse> getAlbumList2(@QueryMap Map<String, String> params, @Query("type") String type, @Query("size") int size, @Query("offset") int offset, @Query("fromYear") Integer fromYear, @Query("toYear") Integer toYear);
|
||||
|
||||
@GET("getRandomSongs")
|
||||
Call<ApiResponse> getRandomSongs(@QueryMap Map<String, String> params, @Query("size") int size, @Query("fromYear") Integer fromYear, @Query("toYear") Integer toYear);
|
||||
|
||||
@GET("getRandomSongs")
|
||||
Call<ApiResponse> getRandomSongs(@QueryMap Map<String, String> params, @Query("size") int size, @Query("fromYear") Integer fromYear, @Query("toYear") Integer toYear, @Query("genre") String genre);
|
||||
|
||||
@GET("getSongsByGenre")
|
||||
Call<ApiResponse> getSongsByGenre(@QueryMap Map<String, String> params, @Query("genre") String genre, @Query("count") int count, @Query("offset") int offset);
|
||||
|
||||
@GET("getNowPlaying")
|
||||
Call<ApiResponse> getNowPlaying(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getStarred")
|
||||
Call<ApiResponse> getStarred(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getStarred2")
|
||||
Call<ApiResponse> getStarred2(@QueryMap Map<String, String> params);
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.bookmarks;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class BookmarksClient {
|
||||
private static final String TAG = "BookmarksClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final BookmarksService bookmarksService;
|
||||
|
||||
public BookmarksClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.bookmarksService = new RetrofitClient(subsonic).getRetrofit().create(BookmarksService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getPlayQueue() {
|
||||
Log.d(TAG, "getPlayQueue()");
|
||||
return bookmarksService.getPlayQueue(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> savePlayQueue(List<String> ids, String current, long position) {
|
||||
Log.d(TAG, "savePlayQueue()");
|
||||
return bookmarksService.savePlayQueue(subsonic.getParams(), ids, current, position);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.bookmarks;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface BookmarksService {
|
||||
@GET("getPlayQueue")
|
||||
Call<ApiResponse> getPlayQueue(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("savePlayQueue")
|
||||
Call<ApiResponse> savePlayQueue(@QueryMap Map<String, String> params, @Query("id") List<String> ids, @Query("current") String current, @Query("position") long position);
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.browsing;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class BrowsingClient {
|
||||
private static final String TAG = "BrowsingClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final BrowsingService browsingService;
|
||||
|
||||
public BrowsingClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.browsingService = new RetrofitClient(subsonic).getRetrofit().create(BrowsingService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getMusicFolders() {
|
||||
Log.d(TAG, "getMusicFolders()");
|
||||
return browsingService.getMusicFolders(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getIndexes(String musicFolderId, Long ifModifiedSince) {
|
||||
Log.d(TAG, "getIndexes()");
|
||||
return browsingService.getIndexes(subsonic.getParams(), musicFolderId, ifModifiedSince);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getMusicDirectory(String id) {
|
||||
Log.d(TAG, "getMusicDirectory()");
|
||||
return browsingService.getMusicDirectory(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getGenres() {
|
||||
Log.d(TAG, "getGenres()");
|
||||
return browsingService.getGenres(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getArtists() {
|
||||
Log.d(TAG, "getArtists()");
|
||||
return browsingService.getArtists(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getArtist(String id) {
|
||||
Log.d(TAG, "getArtist()");
|
||||
return browsingService.getArtist(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getAlbum(String id) {
|
||||
Log.d(TAG, "getAlbum()");
|
||||
return browsingService.getAlbum(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getSong(String id) {
|
||||
Log.d(TAG, "getSong()");
|
||||
return browsingService.getSong(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getVideos() {
|
||||
Log.d(TAG, "getVideos()");
|
||||
return browsingService.getVideos(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getVideoInfo(String id) {
|
||||
Log.d(TAG, "getVideoInfo()");
|
||||
return browsingService.getVideoInfo(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getArtistInfo(String id) {
|
||||
Log.d(TAG, "getArtistInfo()");
|
||||
return browsingService.getArtistInfo(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getArtistInfo2(String id) {
|
||||
Log.d(TAG, "getArtistInfo2()");
|
||||
return browsingService.getArtistInfo2(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getAlbumInfo(String id) {
|
||||
Log.d(TAG, "getAlbumInfo()");
|
||||
return browsingService.getAlbumInfo(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getAlbumInfo2(String id) {
|
||||
Log.d(TAG, "getAlbumInfo2()");
|
||||
return browsingService.getAlbumInfo2(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getSimilarSongs(String id, int count) {
|
||||
Log.d(TAG, "getSimilarSongs()");
|
||||
return browsingService.getSimilarSongs(subsonic.getParams(), id, count);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getSimilarSongs2(String id, int limit) {
|
||||
Log.d(TAG, "getSimilarSongs2()");
|
||||
return browsingService.getSimilarSongs2(subsonic.getParams(), id, limit);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getTopSongs(String artist, int count) {
|
||||
Log.d(TAG, "getTopSongs()");
|
||||
return browsingService.getTopSongs(subsonic.getParams(), artist, count);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.browsing;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface BrowsingService {
|
||||
@GET("getMusicFolders")
|
||||
Call<ApiResponse> getMusicFolders(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getIndexes")
|
||||
Call<ApiResponse> getIndexes(@QueryMap Map<String, String> params, @Query("musicFolderId") String musicFolderId, @Query("ifModifiedSince") Long ifModifiedSince);
|
||||
|
||||
@GET("getMusicDirectory")
|
||||
Call<ApiResponse> getMusicDirectory(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getGenres")
|
||||
Call<ApiResponse> getGenres(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getArtists")
|
||||
Call<ApiResponse> getArtists(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getArtist")
|
||||
Call<ApiResponse> getArtist(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getAlbum")
|
||||
Call<ApiResponse> getAlbum(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getSong")
|
||||
Call<ApiResponse> getSong(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getVideos")
|
||||
Call<ApiResponse> getVideos(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getVideoInfo")
|
||||
Call<ApiResponse> getVideoInfo(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getArtistInfo")
|
||||
Call<ApiResponse> getArtistInfo(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getArtistInfo2")
|
||||
Call<ApiResponse> getArtistInfo2(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getAlbumInfo")
|
||||
Call<ApiResponse> getAlbumInfo(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getAlbumInfo2")
|
||||
Call<ApiResponse> getAlbumInfo2(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getSimilarSongs")
|
||||
Call<ApiResponse> getSimilarSongs(@QueryMap Map<String, String> params, @Query("id") String id, @Query("count") int count);
|
||||
|
||||
@GET("getSimilarSongs2")
|
||||
Call<ApiResponse> getSimilarSongs2(@QueryMap Map<String, String> params, @Query("id") String id, @Query("count") int count);
|
||||
|
||||
@GET("getTopSongs")
|
||||
Call<ApiResponse> getTopSongs(@QueryMap Map<String, String> params, @Query("artist") String artist, @Query("count") int count);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.internetradio;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class InternetRadioClient {
|
||||
private static final String TAG = "InternetRadioClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final InternetRadioService internetRadioService;
|
||||
|
||||
public InternetRadioClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.internetRadioService = new RetrofitClient(subsonic).getRetrofit().create(InternetRadioService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getInternetRadioStations() {
|
||||
Log.d(TAG, "getInternetRadioStations()");
|
||||
return internetRadioService.getInternetRadioStations(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> createInternetRadioStation(String streamUrl, String name, String homepageUrl) {
|
||||
Log.d(TAG, "createInternetRadioStation()");
|
||||
return internetRadioService.createInternetRadioStation(subsonic.getParams(), streamUrl, name, homepageUrl);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> updateInternetRadioStation(String id, String streamUrl, String name, String homepageUrl) {
|
||||
Log.d(TAG, "updateInternetRadioStation()");
|
||||
return internetRadioService.updateInternetRadioStation(subsonic.getParams(), id, streamUrl, name, homepageUrl);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> deleteInternetRadioStation(String id) {
|
||||
Log.d(TAG, "deleteInternetRadioStation()");
|
||||
return internetRadioService.deleteInternetRadioStation(subsonic.getParams(), id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.internetradio;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface InternetRadioService {
|
||||
@GET("getInternetRadioStations")
|
||||
Call<ApiResponse> getInternetRadioStations(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("createInternetRadioStation")
|
||||
Call<ApiResponse> createInternetRadioStation(@QueryMap Map<String, String> params, @Query("streamUrl") String streamUrl, @Query("name") String name, @Query("homepageUrl") String homepageUrl);
|
||||
|
||||
@GET("updateInternetRadioStation")
|
||||
Call<ApiResponse> updateInternetRadioStation(@QueryMap Map<String, String> params, @Query("id") String id, @Query("streamUrl") String streamUrl, @Query("name") String name, @Query("homepageUrl") String homepageUrl);
|
||||
|
||||
@GET("deleteInternetRadioStation")
|
||||
Call<ApiResponse> deleteInternetRadioStation(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.mediaannotation;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class MediaAnnotationClient {
|
||||
private static final String TAG = "MediaAnnotationClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final MediaAnnotationService mediaAnnotationService;
|
||||
|
||||
public MediaAnnotationClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.mediaAnnotationService = new RetrofitClient(subsonic).getRetrofit().create(MediaAnnotationService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> star(String id, String albumId, String artistId) {
|
||||
Log.d(TAG, "star()");
|
||||
return mediaAnnotationService.star(subsonic.getParams(), id, albumId, artistId);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> unstar(String id, String albumId, String artistId) {
|
||||
Log.d(TAG, "unstar()");
|
||||
return mediaAnnotationService.unstar(subsonic.getParams(), id, albumId, artistId);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> setRating(String id, int rating) {
|
||||
Log.d(TAG, "setRating()");
|
||||
return mediaAnnotationService.setRating(subsonic.getParams(), id, rating);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> scrobble(String id, boolean submission) {
|
||||
Log.d(TAG, "scrobble()");
|
||||
return mediaAnnotationService.scrobble(subsonic.getParams(), id, submission);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.mediaannotation;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface MediaAnnotationService {
|
||||
@GET("star")
|
||||
Call<ApiResponse> star(@QueryMap Map<String, String> params, @Query("id") String id, @Query("albumId") String albumId, @Query("artistId") String artistId);
|
||||
|
||||
@GET("unstar")
|
||||
Call<ApiResponse> unstar(@QueryMap Map<String, String> params, @Query("id") String id, @Query("albumId") String albumId, @Query("artistId") String artistId);
|
||||
|
||||
@GET("setRating")
|
||||
Call<ApiResponse> setRating(@QueryMap Map<String, String> params, @Query("id") String id, @Query("rating") int rating);
|
||||
|
||||
@GET("scrobble")
|
||||
Call<ApiResponse> scrobble(@QueryMap Map<String, String> params, @Query("id") String id, @Query("submission") Boolean submission);
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.medialibraryscanning;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class MediaLibraryScanningClient {
|
||||
private static final String TAG = "MediaLibraryScanningClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final MediaLibraryScanningService mediaLibraryScanningService;
|
||||
|
||||
public MediaLibraryScanningClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.mediaLibraryScanningService = new RetrofitClient(subsonic).getRetrofit().create(MediaLibraryScanningService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> startScan() {
|
||||
Log.d(TAG, "startScan()");
|
||||
return mediaLibraryScanningService.startScan(subsonic.getParams());
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getScanStatus() {
|
||||
Log.d(TAG, "getScanStatus()");
|
||||
return mediaLibraryScanningService.getScanStatus(subsonic.getParams());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.medialibraryscanning;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface MediaLibraryScanningService {
|
||||
@GET("startScan")
|
||||
Call<ApiResponse> startScan(@QueryMap Map<String, String> params);
|
||||
|
||||
@GET("getScanStatus")
|
||||
Call<ApiResponse> getScanStatus(@QueryMap Map<String, String> params);
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.mediaretrieval;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class MediaRetrievalClient {
|
||||
private static final String TAG = "MediaRetrievalClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final MediaRetrievalService mediaRetrievalService;
|
||||
|
||||
public MediaRetrievalClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.mediaRetrievalService = new RetrofitClient(subsonic).getRetrofit().create(MediaRetrievalService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> stream(String id, Integer maxBitRate, String format) {
|
||||
Log.d(TAG, "stream()");
|
||||
return mediaRetrievalService.stream(subsonic.getParams(), id, maxBitRate, format);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> download(String id) {
|
||||
Log.d(TAG, "download()");
|
||||
return mediaRetrievalService.download(subsonic.getParams(), id);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getLyrics(String artist, String title) {
|
||||
Log.d(TAG, "getLyrics()");
|
||||
return mediaRetrievalService.getLyrics(subsonic.getParams(), artist, title);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.mediaretrieval;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface MediaRetrievalService {
|
||||
@GET("stream")
|
||||
Call<ApiResponse> stream(@QueryMap Map<String, String> params, @Query("id") String id, @Query("maxBitRate") Integer maxBitRate, @Query("format") String format);
|
||||
|
||||
@GET("download")
|
||||
Call<ApiResponse> download(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
|
||||
@GET("getLyrics")
|
||||
Call<ApiResponse> getLyrics(@QueryMap Map<String, String> params, @Query("artist") String artist, @Query("title") String title);
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.open;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.RetrofitClient;
|
||||
import com.cappielloantonio.tempo.subsonic.Subsonic;
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import retrofit2.Call;
|
||||
|
||||
public class OpenClient {
|
||||
private static final String TAG = "OpenClient";
|
||||
|
||||
private final Subsonic subsonic;
|
||||
private final OpenService openService;
|
||||
|
||||
public OpenClient(Subsonic subsonic) {
|
||||
this.subsonic = subsonic;
|
||||
this.openService = new RetrofitClient(subsonic).getRetrofit().create(OpenService.class);
|
||||
}
|
||||
|
||||
public Call<ApiResponse> getLyricsBySongId(String id) {
|
||||
Log.d(TAG, "getLyricsBySongId()");
|
||||
return openService.getLyricsBySongId(subsonic.getParams(), id);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.cappielloantonio.tempo.subsonic.api.open;
|
||||
|
||||
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import retrofit2.Call;
|
||||
import retrofit2.http.GET;
|
||||
import retrofit2.http.Query;
|
||||
import retrofit2.http.QueryMap;
|
||||
|
||||
public interface OpenService {
|
||||
@GET("getLyricsBySongId")
|
||||
Call<ApiResponse> getLyricsBySongId(@QueryMap Map<String, String> params, @Query("id") String id);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue