build: change of package name

This commit is contained in:
antonio 2023-06-17 15:30:23 +02:00
parent 49afdbe4eb
commit b76a38cb30
274 changed files with 1981 additions and 2161 deletions

View file

@ -0,0 +1,90 @@
package com.cappielloantonio.tempo;
import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import com.cappielloantonio.tempo.helper.ThemeHelper;
import com.cappielloantonio.tempo.subsonic.Subsonic;
import com.cappielloantonio.tempo.subsonic.SubsonicPreferences;
import com.cappielloantonio.tempo.util.Preferences;
import com.google.android.material.color.DynamicColors;
public class App extends Application {
private static App instance;
private static Context context;
private static Subsonic subsonic;
private static SharedPreferences preferences;
@Override
public void onCreate() {
super.onCreate();
DynamicColors.applyToActivitiesIfAvailable(this);
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 SharedPreferences getPreferences() {
if (preferences == null) {
preferences = PreferenceManager.getDefaultSharedPreferences(context);
}
return preferences;
}
private static Subsonic getSubsonicClient() {
String server = Preferences.getServer();
String username = Preferences.getUser();
String password = Preferences.getPassword();
String token = Preferences.getToken();
String salt = Preferences.getSalt();
boolean isLowSecurity = Preferences.isLowScurity();
SubsonicPreferences preferences = new SubsonicPreferences();
preferences.setServerUrl(server);
preferences.setUsername(username);
preferences.setAuthentication(password, token, salt, isLowSecurity);
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);
}
}

View file

@ -0,0 +1,30 @@
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 com.cappielloantonio.tempo.ui.activity.MainActivity;
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);
}
}
}
}

View file

@ -0,0 +1,50 @@
package com.cappielloantonio.tempo.database;
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.QueueDao;
import com.cappielloantonio.tempo.database.dao.RecentSearchDao;
import com.cappielloantonio.tempo.database.dao.ServerDao;
import com.cappielloantonio.tempo.model.Chronology;
import com.cappielloantonio.tempo.model.Download;
import com.cappielloantonio.tempo.model.Queue;
import com.cappielloantonio.tempo.model.RecentSearch;
import com.cappielloantonio.tempo.model.Server;
@Database(
version = 1,
entities = {Queue.class, Server.class, RecentSearch.class, Download.class, Chronology.class}
// autoMigrations = {@AutoMigration(from = 61, to = 62)}
)
@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();
}

View file

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

View file

@ -0,0 +1,20 @@
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 timestamp >= :startDate AND timestamp < :endDate AND server == :server GROUP BY id ORDER BY COUNT(id) DESC LIMIT 9")
LiveData<List<Chronology>> getAllFrom(long startDate, long endDate, String server);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(Chronology chronologyObject);
}

View file

@ -0,0 +1,32 @@
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, track ASC")
LiveData<List<Download>> getAll();
@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")
void deleteAll();
}

View file

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

View file

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

View file

@ -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 GROUP BY search ORDER BY search DESC LIMIT :limit")
List<String> getRecent(int limit);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(RecentSearch search);
@Delete
void delete(RecentSearch search);
}

View file

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

View file

@ -0,0 +1,23 @@
package com.cappielloantonio.tempo.glide;
import android.content.Context;
import androidx.annotation.NonNull;
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.module.AppGlideModule;
import com.bumptech.glide.request.RequestOptions;
import com.cappielloantonio.tempo.util.Preferences;
@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));
}
}

View file

@ -0,0 +1,94 @@
package com.cappielloantonio.tempo.glide;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.util.Log;
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.signature.ObjectKey;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.util.Preferences;
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 static RequestOptions createRequestOptions(Context context, String item) {
return new RequestOptions()
.placeholder(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.fallback(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.error(new ColorDrawable(SurfaceColors.SURFACE_5.getColor(context)))
.diskCacheStrategy(DEFAULT_DISK_CACHE_STRATEGY)
.signature(new ObjectKey(item != null ? item : 0))
.transform(new CenterCrop(), new RoundedCorners(CustomGlideRequest.CORNER_RADIUS));
}
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(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 class Builder {
private final RequestManager requestManager;
private Object item;
private Builder(Context context, String item) {
this.requestManager = Glide.with(context);
if (item != null && !Preferences.isDataSavingMode()) {
this.item = createUrl(item, Preferences.getImageSize());
}
requestManager.applyDefaultRequestOptions(createRequestOptions(context, item));
}
public static Builder from(Context context, String item) {
return new Builder(context, item);
}
public RequestBuilder<Drawable> build() {
return requestManager
.load(item)
.transition(DrawableTransitionOptions.withCrossFade());
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.interfaces;
import android.os.Bundle;
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 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) {}
}

View file

@ -0,0 +1,6 @@
package com.cappielloantonio.tempo.interfaces;
public interface DecadesCallback {
void onLoadYear(int year);
}

View file

@ -0,0 +1,10 @@
package com.cappielloantonio.tempo.interfaces;
import java.util.List;
public interface MediaCallback {
void onError(Exception exception);
void onLoadMedia(List<?> media);
}

View file

@ -0,0 +1,5 @@
package com.cappielloantonio.tempo.interfaces;
public interface MediaIndexCallback {
void onRecovery(int index);
}

View file

@ -0,0 +1,6 @@
package com.cappielloantonio.tempo.interfaces;
public interface PodcastCallback {
void onDismiss();
}

View file

@ -0,0 +1,6 @@
package com.cappielloantonio.tempo.interfaces;
public interface RadioCallback {
void onDismiss();
}

View file

@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.interfaces;
public interface ScanCallback {
void onError(Exception exception);
void onSuccess(boolean isScanning, long count);
}

View file

@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.interfaces;
public interface SystemCallback {
void onError(Exception exception);
void onSuccess(String password, String token, String salt);
}

View file

@ -0,0 +1,57 @@
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.*
@Keep
@Parcelize
@Entity(tableName = "chronology")
class Chronology(@PrimaryKey override val id: String) : Child(id) {
@ColumnInfo(name = "timestamp")
var timestamp: Long = System.currentTimeMillis()
@ColumnInfo(name = "server")
var server: String? = null
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")
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
}
}

View file

@ -0,0 +1,55 @@
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) : Child(id) {
@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;
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
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
}
}

View 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) : Child(id) {
@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
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
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
}
}

View file

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

View file

@ -0,0 +1,35 @@
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 = "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 = "timestamp")
val timestamp: Long,
@ColumnInfo(name = "low_security", defaultValue = "false")
val isLowSecurity: Boolean
) : Parcelable

View file

@ -0,0 +1,293 @@
package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
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.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 {
private static final String TAG = "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());
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
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 star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(null, id, null)
.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 unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(null, id, null)
.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>> 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) {
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) {
List<AlbumID3> albums = response.body().getSubsonicResponse().getArtist().getAlbums();
albums.sort(Comparator.comparing(AlbumID3::getYear));
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 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(first -> getLastAlbum(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) {
if (response.body().getSubsonicResponse().getAlbumList2().getAlbums().size() > 0 && !response.body().getSubsonicResponse().getAlbumList2().getAlbums().isEmpty()) {
callback.onLoadYear(response.body().getSubsonicResponse().getAlbumList2().getAlbums().get(0).getYear());
} else {
callback.onLoadYear(-1);
}
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
callback.onLoadYear(-1);
}
});
}
}

View file

@ -0,0 +1,318 @@
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.ArtistID3;
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.Collections;
import java.util.List;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class ArtistRepository {
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<>();
for (IndexID3 index : response.body().getSubsonicResponse().getArtists().getIndices()) {
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;
}
/*
* Metodo che mi restituisce le informazioni essenzionali dell'artista (cover, numero di album...)
*/
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;
}
/*
* Metodo che mi restituisce le informazioni complete dell'artista (bio, immagini prese da last.fm, artisti simili...)
*/
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 star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(null, null, 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) {
}
});
}
public void unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(null, null, 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) {
}
});
}
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()
.getTopSongs(artist.getName(), 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) {
List<Child> songs = response.body().getSubsonicResponse().getTopSongs().getSongs();
if (songs != null && !songs.isEmpty()) {
Collections.shuffle(songs);
}
randomSongs.setValue(songs);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
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);
}
}

View file

@ -0,0 +1,59 @@
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>> getThisWeek(String server) {
Calendar calendar = Calendar.getInstance();
Calendar first = (Calendar) calendar.clone();
first.add(Calendar.DAY_OF_WEEK, first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK));
Calendar last = (Calendar) first.clone();
last.add(Calendar.DAY_OF_YEAR, 6);
return chronologyDao.getAllFrom(first.getTime().getTime(), last.getTime().getTime(), server);
}
public LiveData<List<Chronology>> getLastWeek(String server) {
Calendar calendar = Calendar.getInstance();
Calendar first = (Calendar) calendar.clone();
first.add(Calendar.DAY_OF_WEEK, first.getFirstDayOfWeek() - first.get(Calendar.DAY_OF_WEEK) - 6);
Calendar last = (Calendar) first.clone();
last.add(Calendar.DAY_OF_YEAR, 6);
return chronologyDao.getAllFrom(first.getTime().getTime(), last.getTime().getTime(), server);
}
public 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);
}
}
}

View file

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

View file

@ -0,0 +1,120 @@
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.model.Download;
import java.util.List;
public class DownloadRepository {
private final DownloadDao downloadDao = AppDatabase.getInstance().downloadDao();
public LiveData<List<Download>> getLiveDownload() {
return downloadDao.getAll();
}
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();
}
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);
}
}
}

View file

@ -0,0 +1,50 @@
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.List;
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().getGenres() != null) {
List<Genre> genreList = response.body().getSubsonicResponse().getGenres().getGenres();
if (random) {
Collections.shuffle(genreList);
}
if (size != -1) {
genres.setValue(genreList.subList(0, Math.min(size, genreList.size())));
} else {
genres.setValue(genreList);
}
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
return genres;
}
}

View file

@ -0,0 +1,141 @@
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.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 {
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 void addSongToPlaylist(String playlistId, ArrayList<String> songsId) {
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) {
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
}
});
}
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) {
Log.d("PLAYLIST", response.toString());
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d("PLAYLIST", t.toString());
}
});
}
public void updatePlaylist(String playlistId, String name, boolean isPublic, ArrayList<String> songIdToAdd, ArrayList<Integer> songIndexToRemove) {
App.getSubsonicClientInstance(false)
.getPlaylistClient()
.updatePlaylist(playlistId, name, isPublic, songIdToAdd, songIndexToRemove)
.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 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) {
}
});
}
}

View file

@ -0,0 +1,136 @@
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) {
Log.d(TAG, "onFailure()");
}
});
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) {
}
});
}
}

View file

@ -0,0 +1,363 @@
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();
}
}
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();
}
for (int i = 0; i < toAdd.size(); i++) {
Queue queueItem = new Queue(toAdd.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;
}
}
}

View file

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

View file

@ -0,0 +1,50 @@
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().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().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()));
}
});
}
}

View file

@ -0,0 +1,172 @@
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.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<SearchResult3> search(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) {
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) {
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(int limit) {
List<String> recent = new ArrayList<>();
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
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 final int limit;
private List<String> recent = new ArrayList<>();
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
this.recentSearchDao = recentSearchDao;
this.limit = limit;
}
@Override
public void run() {
recent = recentSearchDao.getRecent(limit);
}
public List<String> getRecent() {
return recent;
}
}
}

View file

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

View file

@ -0,0 +1,271 @@
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.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(Child song, int count) {
MutableLiveData<List<Child>> instantMix = new MutableLiveData<>();
App.getSubsonicClientInstance(false)
.getBrowsingClient()
.getSimilarSongs2(song.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) {
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) {
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
}
randomSongsSample.setValue(songs);
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
Log.d(TAG, "onFailure: ");
}
});
return randomSongsSample;
}
public void scrobble(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.scrobble(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) {
}
});
}
public void star(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.star(id, null, null)
.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 unstar(String id) {
App.getSubsonicClientInstance(false)
.getMediaAnnotationClient()
.unstar(id, null, null)
.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<>();
Log.d(TAG, "onScrolled PAGE: " + page);
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) {
Log.d(TAG, "onFailure: ");
}
});
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;
}
}

View file

@ -0,0 +1,68 @@
package com.cappielloantonio.tempo.repository;
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import com.cappielloantonio.tempo.App;
import com.cappielloantonio.tempo.interfaces.SystemCallback;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import com.cappielloantonio.tempo.subsonic.models.ResponseStatus;
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().getValue() + " - " + 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<Boolean> ping() {
MutableLiveData<Boolean> 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(true);
}
}
@Override
public void onFailure(@NonNull Call<ApiResponse> call, @NonNull Throwable t) {
pingResult.postValue(false);
}
});
return pingResult;
}
}

View file

@ -0,0 +1,126 @@
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 HashMap<String, Download> downloads;
private final DownloadIndex downloadIndex;
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) {
@Nullable Download download = downloads.get(mediaItem.mediaId);
return download != null && download.state != Download.STATE_FAILED;
}
public boolean areDownloaded(List<MediaItem> mediaItems) {
for (MediaItem mediaItem : mediaItems) {
@Nullable Download download = downloads.get(mediaItem.mediaId);
if (download != null && download.state != Download.STATE_FAILED) {
return true;
}
}
return false;
}
public void download(MediaItem mediaItem, com.cappielloantonio.tempo.model.Download download) {
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);
}
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));
}
}
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);
}
}
private static DownloadRepository getDownloadRepository() {
return new DownloadRepository();
}
public static void insertDatabase(com.cappielloantonio.tempo.model.Download download) {
getDownloadRepository().insert(download);
}
public static void deleteDatabase(String id) {
getDownloadRepository().delete(id);
}
public static void updateDatabase(String id) {
getDownloadRepository().update(id);
}
}

View file

@ -0,0 +1,87 @@
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.common.util.Util;
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 int nextNotificationId;
public TerminalStateNotificationHelper(Context context, DownloadNotificationHelper notificationHelper, int firstNotificationId) {
this.context = context.getApplicationContext();
this.notificationHelper = notificationHelper;
nextNotificationId = firstNotificationId;
}
@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, Util.fromUtf8Bytes(download.request.data));
DownloaderManager.updateDatabase(download.request.id);
} else if (download.state == Download.STATE_FAILED) {
notification = notificationHelper.buildDownloadFailedNotification(context, R.drawable.ic_error, null, Util.fromUtf8Bytes(download.request.data));
} else {
return;
}
NotificationUtil.setNotification(context, nextNotificationId++, notification);
}
@Override
public void onDownloadRemoved(@NonNull DownloadManager downloadManager, Download download) {
DownloaderManager.deleteDatabase(download.request.id);
}
}
}

View file

@ -0,0 +1,310 @@
package com.cappielloantonio.tempo.service;
import androidx.media3.common.MediaItem;
import androidx.media3.session.MediaBrowser;
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.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import java.util.concurrent.ExecutionException;
public class MediaManager {
private static final String TAG = "MediaManager";
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()) {
mediaBrowserListenableFuture.get().clearMediaItems();
mediaBrowserListenableFuture.get().setMediaItems(MappingUtil.mapMediaItems(media));
mediaBrowserListenableFuture.get().prepare();
mediaBrowserListenableFuture.get().seekTo(startIndex, 0);
mediaBrowserListenableFuture.get().play();
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 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 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 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) {
if (mediaItem != null) {
getSongRepository().scrobble(mediaItem.mediaMetadata.extras.getString("id"));
}
}
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);
}
}
public static void clearDatabase() {
getQueueRepository().deleteAll();
}
}

View file

@ -0,0 +1,318 @@
package com.cappielloantonio.tempo.service
import android.annotation.SuppressLint
import android.app.PendingIntent.FLAG_IMMUTABLE
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.TaskStackBuilder
import android.content.Intent
import android.os.Bundle
import androidx.media3.cast.CastPlayer
import androidx.media3.cast.SessionAvailabilityListener
import androidx.media3.common.*
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.session.*
import androidx.media3.session.MediaSession.ControllerInfo
import com.cappielloantonio.tempo.R
import com.cappielloantonio.tempo.ui.activity.MainActivity
import com.cappielloantonio.tempo.util.Constants
import com.cappielloantonio.tempo.util.DownloadUtil
import com.cappielloantonio.tempo.util.UIUtil
import com.google.android.gms.cast.framework.CastContext
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@UnstableApi
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
private val TAG = "MediaService"
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
private lateinit var player: ExoPlayer
private lateinit var castPlayer: CastPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var customCommands: List<CommandButton>
private var customLayout = ImmutableList.of<CommandButton>()
companion object {
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
}
override fun onCreate() {
super.onCreate()
initializeCustomCommands()
initializePlayer()
initializeCastPlayer()
initializeMediaLibrarySession()
initializePlayerListener()
setPlayer(
null,
if (this::castPlayer.isInitialized && castPlayer.isCastSessionAvailable) castPlayer else player
)
}
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
override fun onDestroy() {
releasePlayer()
super.onDestroy()
}
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
override fun onConnect(
session: MediaSession,
controller: ControllerInfo
): MediaSession.ConnectionResult {
val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
customCommands.forEach { commandButton ->
// TODO: Aggiungere i comandi personalizzati
// commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
}
return MediaSession.ConnectionResult.accept(
availableSessionCommands.build(),
connectionResult.availablePlayerCommands
)
}
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
}
}
override fun onCustomCommand(
session: MediaSession,
controller: ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
player.shuffleModeEnabled = true
customLayout = ImmutableList.of(customCommands[1])
session.setCustomLayout(customLayout)
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
player.shuffleModeEnabled = false
customLayout = ImmutableList.of(customCommands[0])
session.setCustomLayout(customLayout)
}
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
/* override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
}
override fun onGetItem(
session: MediaLibrarySession,
browser: ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onSubscribe(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
params: LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
session.notifyChildrenChanged(browser, parentId, children.size, params)
return Futures.immediateFuture(LibraryResult.ofVoid())
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}*/
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: ControllerInfo,
mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> {
val updatedMediaItems = mediaItems.map {
it.buildUpon()
.setUri(it.requestMetadata.mediaUri)
.setMediaMetadata(it.mediaMetadata)
.setMimeType(MimeTypes.BASE_TYPE_AUDIO)
.build()
}
return Futures.immediateFuture(updatedMediaItems)
}
}
private fun initializeCustomCommands() {
customCommands =
listOf(
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
),
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
)
)
customLayout = ImmutableList.of(customCommands[0])
}
private fun initializePlayer() {
player = ExoPlayer.Builder(this)
.setRenderersFactory(getRenderersFactory())
.setMediaSourceFactory(getMediaSourceFactory())
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setHandleAudioBecomingNoisy(true)
.setWakeMode(C.WAKE_MODE_NETWORK)
.build()
}
private fun initializeCastPlayer() {
if (UIUtil.isCastApiAvailable(this)) {
castPlayer = CastPlayer(CastContext.getSharedInstance(this))
castPlayer.setSessionAvailabilityListener(this)
}
}
private fun initializeMediaLibrarySession() {
val sessionActivityPendingIntent =
TaskStackBuilder.create(this).run {
addNextIntent(Intent(this@MediaService, MainActivity::class.java))
getPendingIntent(0, FLAG_IMMUTABLE or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent)
.build()
if (!customLayout.isEmpty()) {
mediaLibrarySession.setCustomLayout(customLayout)
}
}
private fun initializePlayerListener() {
player.addListener(object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (mediaItem == null) return
if(reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {
MediaManager.setLastPlayedTimestamp(mediaItem)
}
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (!isPlaying) {
MediaManager.setPlayingPausedTimestamp(
player.currentMediaItem,
player.currentPosition
)
}
}
override fun onPositionDiscontinuity(
oldPosition: Player.PositionInfo,
newPosition: Player.PositionInfo,
reason: Int
) {
super.onPositionDiscontinuity(oldPosition, newPosition, reason)
if(reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION) {
if (oldPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.scrobble(oldPosition.mediaItem)
MediaManager.saveChronology(oldPosition.mediaItem)
}
if (newPosition.mediaItem?.mediaMetadata?.extras?.getString("type") == Constants.MEDIA_TYPE_MUSIC) {
MediaManager.setLastPlayedTimestamp(newPosition.mediaItem)
}
}
}
})
}
private fun setPlayer(oldPlayer: Player?, newPlayer: Player) {
if (oldPlayer === newPlayer) return
oldPlayer?.stop()
mediaLibrarySession.player = newPlayer
}
private fun releasePlayer() {
if (this::castPlayer.isInitialized) castPlayer.setSessionAvailabilityListener(null)
if (this::castPlayer.isInitialized) castPlayer.release()
player.release()
mediaLibrarySession.release()
}
@SuppressLint("PrivateResource")
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
return CommandButton.Builder()
.setDisplayName(
getString(
if (isOn) R.string.exo_controls_shuffle_on_description
else R.string.exo_controls_shuffle_off_description
)
)
.setSessionCommand(sessionCommand)
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
.build()
}
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
/* Do nothing. */
}
private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false)
private fun getMediaSourceFactory() =
DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this))
override fun onCastSessionAvailable() {
setPlayer(player, castPlayer)
}
override fun onCastSessionUnavailable() {
setPlayer(castPlayer, player)
}
}

View file

@ -0,0 +1,59 @@
package com.cappielloantonio.tempo.subsonic
import com.cappielloantonio.tempo.App
import com.cappielloantonio.tempo.subsonic.utils.CacheUtil
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.concurrent.TimeUnit
class RetrofitClient(subsonic: Subsonic) {
var retrofit: Retrofit
init {
retrofit = Retrofit.Builder()
.baseUrl(subsonic.url)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.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())
}
}

View file

@ -0,0 +1,144 @@
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.playlist.PlaylistClient;
import com.cappielloantonio.tempo.subsonic.api.podcast.PodcastClient;
import com.cappielloantonio.tempo.subsonic.api.searching.SearchingClient;
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;
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 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;
}
}

View file

@ -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 = "Tempo";
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);
}
}
}

View file

@ -0,0 +1,56 @@
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> 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());
}
}

View file

@ -0,0 +1,33 @@
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("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);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = "BrowsingClient";
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) {
Log.d(TAG, "scrobble()");
return mediaAnnotationService.scrobble(subsonic.getParams(), id);
}
}

View file

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

View file

@ -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 = "SystemClient";
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());
}
}

View file

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

View file

@ -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 = "BrowsingClient";
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);
}
}

View file

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

View file

@ -0,0 +1,48 @@
package com.cappielloantonio.tempo.subsonic.api.playlist;
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.ArrayList;
import retrofit2.Call;
public class PlaylistClient {
private static final String TAG = "BrowsingClient";
private final Subsonic subsonic;
private final PlaylistService playlistService;
public PlaylistClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.playlistService = new RetrofitClient(subsonic).getRetrofit().create(PlaylistService.class);
}
public Call<ApiResponse> getPlaylists() {
Log.d(TAG, "getPlaylists()");
return playlistService.getPlaylists(subsonic.getParams());
}
public Call<ApiResponse> getPlaylist(String id) {
Log.d(TAG, "getPlaylist()");
return playlistService.getPlaylist(subsonic.getParams(), id);
}
public Call<ApiResponse> createPlaylist(String playlistId, String name, ArrayList<String> songsId) {
Log.d(TAG, "createPlaylist()");
return playlistService.createPlaylist(subsonic.getParams(), playlistId, name, songsId);
}
public Call<ApiResponse> updatePlaylist(String playlistId, String name, boolean isPublic, ArrayList<String> songIdToAdd, ArrayList<Integer> songIndexToRemove) {
Log.d(TAG, "updatePlaylist()");
return playlistService.updatePlaylist(subsonic.getParams(), playlistId, name, isPublic, songIdToAdd, songIndexToRemove);
}
public Call<ApiResponse> deletePlaylist(String id) {
Log.d(TAG, "deletePlaylist()");
return playlistService.deletePlaylist(subsonic.getParams(), id);
}
}

View file

@ -0,0 +1,28 @@
package com.cappielloantonio.tempo.subsonic.api.playlist;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import java.util.ArrayList;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.QueryMap;
public interface PlaylistService {
@GET("getPlaylists")
Call<ApiResponse> getPlaylists(@QueryMap Map<String, String> params);
@GET("getPlaylist")
Call<ApiResponse> getPlaylist(@QueryMap Map<String, String> params, @Query("id") String id);
@GET("createPlaylist")
Call<ApiResponse> createPlaylist(@QueryMap Map<String, String> params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("songId") ArrayList<String> songsId);
@GET("updatePlaylist")
Call<ApiResponse> updatePlaylist(@QueryMap Map<String, String> params, @Query("playlistId") String playlistId, @Query("name") String name, @Query("public") boolean isPublic, @Query("songIdToAdd") ArrayList<String> songIdToAdd, @Query("songIndexToRemove") ArrayList<Integer> songIndexToRemove);
@GET("deletePlaylist")
Call<ApiResponse> deletePlaylist(@QueryMap Map<String, String> params, @Query("id") String id);
}

View file

@ -0,0 +1,51 @@
package com.cappielloantonio.tempo.subsonic.api.podcast;
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 PodcastClient {
private static final String TAG = "SystemClient";
private final Subsonic subsonic;
private final PodcastService podcastService;
public PodcastClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.podcastService = new RetrofitClient(subsonic).getRetrofit().create(PodcastService.class);
}
public Call<ApiResponse> getPodcasts(boolean includeEpisodes, String channelId) {
Log.d(TAG, "getPodcasts()");
return podcastService.getPodcasts(subsonic.getParams(), includeEpisodes, channelId);
}
public Call<ApiResponse> getNewestPodcasts(int count) {
Log.d(TAG, "getNewestPodcasts()");
return podcastService.getNewestPodcasts(subsonic.getParams(), count);
}
public Call<ApiResponse> refreshPodcasts() {
Log.d(TAG, "refreshPodcasts()");
return podcastService.refreshPodcasts(subsonic.getParams());
}
public Call<ApiResponse> createPodcastChannel(String url) {
Log.d(TAG, "createPodcastChannel()");
return podcastService.createPodcastChannel(subsonic.getParams(), url);
}
public Call<ApiResponse> deletePodcastChannel(String channelId) {
Log.d(TAG, "deletePodcastChannel()");
return podcastService.deletePodcastChannel(subsonic.getParams(), channelId);
}
public Call<ApiResponse> deletePodcastEpisode(String episodeId) {
Log.d(TAG, "deletePodcastEpisode()");
return podcastService.deletePodcastEpisode(subsonic.getParams(), episodeId);
}
}

View file

@ -0,0 +1,30 @@
package com.cappielloantonio.tempo.subsonic.api.podcast;
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 PodcastService {
@GET("getPodcasts")
Call<ApiResponse> getPodcasts(@QueryMap Map<String, String> params, @Query("includeEpisodes") boolean includeEpisodes, @Query("id") String id);
@GET("getNewestPodcasts")
Call<ApiResponse> getNewestPodcasts(@QueryMap Map<String, String> params, @Query("count") int count);
@GET("refreshPodcasts")
Call<ApiResponse> refreshPodcasts(@QueryMap Map<String, String> params);
@GET("createPodcastChannel")
Call<ApiResponse> createPodcastChannel(@QueryMap Map<String, String> params, @Query("url") String url);
@GET("deletePodcastChannel")
Call<ApiResponse> deletePodcastChannel(@QueryMap Map<String, String> params, @Query("id") String id);
@GET("deletePodcastEpisode")
Call<ApiResponse> deletePodcastEpisode(@QueryMap Map<String, String> params, @Query("id") String id);
}

View file

@ -0,0 +1,31 @@
package com.cappielloantonio.tempo.subsonic.api.searching;
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 SearchingClient {
private static final String TAG = "BrowsingClient";
private final Subsonic subsonic;
private final SearchingService searchingService;
public SearchingClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.searchingService = new RetrofitClient(subsonic).getRetrofit().create(SearchingService.class);
}
public Call<ApiResponse> search2(String query, int songCount, int albumCount, int artistCount) {
Log.d(TAG, "search2()");
return searchingService.search2(subsonic.getParams(), query, songCount, albumCount, artistCount);
}
public Call<ApiResponse> search3(String query, int songCount, int albumCount, int artistCount) {
Log.d(TAG, "search3()");
return searchingService.search3(subsonic.getParams(), query, songCount, albumCount, artistCount);
}
}

View file

@ -0,0 +1,18 @@
package com.cappielloantonio.tempo.subsonic.api.searching;
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 SearchingService {
@GET("search2")
Call<ApiResponse> search2(@QueryMap Map<String, String> params, @Query("query") String query, @Query("songCount") int songCount, @Query("albumCount") int albumCount, @Query("artistCount") int artistCount);
@GET("search3")
Call<ApiResponse> search3(@QueryMap Map<String, String> params, @Query("query") String query, @Query("songCount") int songCount, @Query("albumCount") int albumCount, @Query("artistCount") int artistCount);
}

View file

@ -0,0 +1,31 @@
package com.cappielloantonio.tempo.subsonic.api.system;
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 SystemClient {
private static final String TAG = "SystemClient";
private final Subsonic subsonic;
private final SystemService systemService;
public SystemClient(Subsonic subsonic) {
this.subsonic = subsonic;
this.systemService = new RetrofitClient(subsonic).getRetrofit().create(SystemService.class);
}
public Call<ApiResponse> ping() {
Log.d(TAG, "ping()");
return systemService.ping(subsonic.getParams());
}
public Call<ApiResponse> getLicense() {
Log.d(TAG, "getLicense()");
return systemService.getLicense(subsonic.getParams());
}
}

View file

@ -0,0 +1,17 @@
package com.cappielloantonio.tempo.subsonic.api.system;
import com.cappielloantonio.tempo.subsonic.base.ApiResponse;
import java.util.Map;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;
public interface SystemService {
@GET("ping")
Call<ApiResponse> ping(@QueryMap Map<String, String> params);
@GET("getLicense")
Call<ApiResponse> getLicense(@QueryMap Map<String, String> params);
}

View file

@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.subsonic.base
import com.cappielloantonio.tempo.subsonic.models.SubsonicResponse
import com.google.gson.annotations.SerializedName
class ApiResponse {
@SerializedName("subsonic-response")
var subsonicResponse: SubsonicResponse? = null
}

View file

@ -0,0 +1,59 @@
package com.cappielloantonio.tempo.subsonic.base;
import androidx.annotation.NonNull;
public class Version implements Comparable<Version> {
private static final String VERSION_PATTERN = "\\d+(\\.\\d+)*";
private final String versionString;
public static Version of(String versionString) {
return new Version(versionString);
}
private Version(String versionString) {
if (versionString == null || !versionString.matches(VERSION_PATTERN)) {
throw new IllegalArgumentException("Invalid version format");
}
this.versionString = versionString;
}
public String getVersionString() {
return versionString;
}
public boolean isLowerThan(Version version) {
return compareTo(version) < 0;
}
@Override
public int compareTo(Version that) {
if (that == null) {
return 1;
}
String[] thisParts = this.getVersionString().split("\\.");
String[] thatParts = that.getVersionString().split("\\.");
int length = Math.max(thisParts.length, thatParts.length);
for (int i = 0; i < length; i++) {
int thisPart = i < thisParts.length ? Integer.parseInt(thisParts[i]) : 0;
int thatPart = i < thatParts.length ? Integer.parseInt(thatParts[i]) : 0;
if (thisPart < thatPart) {
return -1;
}
if (thisPart > thatPart) {
return 1;
}
}
return 0;
}
@NonNull
@Override
public String toString() {
return versionString;
}
}

View file

@ -0,0 +1,25 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
open class AlbumID3 : Parcelable {
var id: String? = null
var name: String? = null
var artist: String? = null
var artistId: String? = null
@SerializedName("coverArt")
var coverArtId: String? = null
var songCount: Int? = 0
var duration: Int? = 0
var playCount: Long? = null
var created: Date? = null
var starred: Date? = null
var year: Int = 0
var genre: String? = null
}

View file

@ -0,0 +1,11 @@
package com.cappielloantonio.tempo.subsonic.models
class AlbumInfo {
var notes: String? = null
var musicBrainzId: String? = null
var lastFmUrl: String? = null
var smallImageUrl: String? = null
var mediumImageUrl: String? = null
var largeImageUrl: String? = null
}

View file

@ -0,0 +1,5 @@
package com.cappielloantonio.tempo.subsonic.models
class AlbumList {
var albums: List<Child>? = null
}

View file

@ -0,0 +1,8 @@
package com.cappielloantonio.tempo.subsonic.models
import com.google.gson.annotations.SerializedName
class AlbumList2 {
@SerializedName("album")
var albums: List<AlbumID3>? = null
}

View file

@ -0,0 +1,11 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
class AlbumWithSongsID3 : AlbumID3(), Parcelable {
@SerializedName("song")
var songs: List<Child>? = null
}

View file

@ -0,0 +1,14 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
import java.util.Date
@Parcelize
class Artist : Parcelable {
var id: String? = null
var name: String? = null
var starred: Date? = null
var userRating: Int? = null
var averageRating: Double? = null
}

View file

@ -0,0 +1,17 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
open class ArtistID3 : Parcelable {
var id: String? = null
var name: String? = null
@SerializedName("coverArt")
var coverArtId: String? = null
var albumCount = 0
var starred: Date? = null
}

View file

@ -0,0 +1,5 @@
package com.cappielloantonio.tempo.subsonic.models
class ArtistInfo : ArtistInfoBase() {
var similarArtists: List<Artist>? = null
}

View file

@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.subsonic.models
import com.google.gson.annotations.SerializedName
import java.util.*
class ArtistInfo2 : ArtistInfoBase() {
@SerializedName("similarArtist")
var similarArtists: List<SimilarArtistID3>? = emptyList()
}

View file

@ -0,0 +1,10 @@
package com.cappielloantonio.tempo.subsonic.models
open class ArtistInfoBase {
var biography: String? = null
var musicBrainzId: String? = null
var lastFmUrl: String? = null
var smallImageUrl: String? = null
var mediumImageUrl: String? = null
var largeImageUrl: String? = null
}

View file

@ -0,0 +1,11 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
class ArtistWithAlbumsID3 : ArtistID3(), Parcelable {
@SerializedName("album")
var albums: List<AlbumID3>? = null
}

View file

@ -0,0 +1,9 @@
package com.cappielloantonio.tempo.subsonic.models
import com.google.gson.annotations.SerializedName
class ArtistsID3 {
@SerializedName("index")
var indices: List<IndexID3>? = null
var ignoredArticles: String? = null
}

View file

@ -0,0 +1,7 @@
package com.cappielloantonio.tempo.subsonic.models
class AudioTrack {
var id: String? = null
var name: String? = null
var languageCode: String? = null
}

View file

@ -0,0 +1,12 @@
package com.cappielloantonio.tempo.subsonic.models
import java.util.*
class Bookmark {
var entry: Child? = null
var position: Long = 0
var username: String? = null
var comment: String? = null
var created: Date? = null
var changed: Date? = null
}

View file

@ -0,0 +1,5 @@
package com.cappielloantonio.tempo.subsonic.models
class Bookmarks {
var bookmarks: List<Bookmark>? = null
}

View file

@ -0,0 +1,6 @@
package com.cappielloantonio.tempo.subsonic.models
class Captions {
var id: String? = null
var name: String? = null
}

View file

@ -0,0 +1,7 @@
package com.cappielloantonio.tempo.subsonic.models
class ChatMessage {
var username: String? = null
var time: Long = 0
var message: String? = null
}

View file

@ -0,0 +1,5 @@
package com.cappielloantonio.tempo.subsonic.models
class ChatMessages {
var chatMessages: List<ChatMessage>? = null
}

View file

@ -0,0 +1,110 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.PrimaryKey
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.*
@Parcelize
open class Child(
@PrimaryKey
@ColumnInfo(name = "id")
open val id: String,
@ColumnInfo(name = "parent_id")
@SerializedName("parent")
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
@SerializedName("genre")
var genre: String? = null,
@ColumnInfo(name = "cover_art_id")
@SerializedName("coverArt")
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")
@SerializedName("bitRate")
var bitrate: Int? = null,
@ColumnInfo
var path: String? = null,
@ColumnInfo(name = "is_video")
@SerializedName("isVideo")
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
) : Parcelable

View file

@ -0,0 +1,20 @@
package com.cappielloantonio.tempo.subsonic.models
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.Date
@Parcelize
class Directory : Parcelable {
@SerializedName("child")
var children: List<Child>? = null
var id: String? = null
@SerializedName("parent")
var parentId: String? = null
var name: String? = null
var starred: Date? = null
var userRating: Int? = null
var averageRating: Double? = null
var playCount: Long? = null
}

View file

@ -0,0 +1,7 @@
package com.cappielloantonio.tempo.subsonic.models
class Error {
var code: ErrorCode? = null
var message: String? = null
}

View file

@ -0,0 +1,16 @@
package com.cappielloantonio.tempo.subsonic.models
class ErrorCode(val value: Int) {
companion object {
var GENERIC_ERROR = 0
var REQUIRED_PARAMETER_MISSING = 10
var INCOMPATIBLE_VERSION_CLIENT = 20
var INCOMPATIBLE_VERSION_SERVER = 30
var WRONG_USERNAME_OR_PASSWORD = 40
var TOKEN_AUTHENTICATION_NOT_SUPPORTED = 41
var USER_NOT_AUTHORIZED = 50
var TRIAL_PERIOD_OVER = 60
var DATA_NOT_FOUND = 70
}
}

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