From 78a4006ed6fc631a64a9d9e0a6ba9dce273d7b35 Mon Sep 17 00:00:00 2001 From: CappielloAntonio Date: Sun, 8 Aug 2021 19:21:56 +0200 Subject: [PATCH] Multi server/user implementation --- .idea/misc.xml | 10 ++ .../play/adapter/ServerAdapter.java | 127 +++++++++++++++ .../play/database/AppDatabase.java | 6 +- .../play/database/dao/DownloadDao.java | 8 +- .../play/database/dao/QueueDao.java | 16 +- .../play/database/dao/RecentSearchDao.java | 3 - .../play/database/dao/ServerDao.java | 25 +++ .../cappielloantonio/play/model/Album.java | 4 +- .../cappielloantonio/play/model/Download.java | 14 +- .../cappielloantonio/play/model/Queue.java | 14 +- .../play/model/RecentSearch.java | 4 +- .../cappielloantonio/play/model/Server.java | 99 ++++++++++++ .../play/repository/AlbumRepository.java | 31 ++-- .../play/repository/DownloadRepository.java | 6 +- .../play/repository/QueueRepository.java | 12 +- .../play/repository/ServerRepository.java | 70 ++++++++ .../play/ui/activity/MainActivity.java | 5 +- .../play/ui/fragment/LoginFragment.java | 150 +++++++++++------- .../play/ui/fragment/SettingsFragment.java | 20 +++ .../fragment/dialog/ServerSignupDialog.java | 142 +++++++++++++++++ .../play/util/PreferenceUtil.java | 11 ++ .../cappielloantonio/play/util/QueueUtil.java | 4 +- .../play/viewmodel/LoginViewModel.java | 48 ++++++ app/src/main/res/drawable-v24/ic_add.xml | 9 ++ .../main/res/layout/dialog_server_signup.xml | 97 +++++++++++ app/src/main/res/layout/fragment_login.xml | 138 ++++++---------- app/src/main/res/layout/item_login_server.xml | 59 +++++++ app/src/main/res/menu/login_page_menu.xml | 10 ++ app/src/main/res/navigation/nav_graph.xml | 5 + app/src/main/res/xml/global_preferences.xml | 4 + 30 files changed, 959 insertions(+), 192 deletions(-) create mode 100644 app/src/main/java/com/cappielloantonio/play/adapter/ServerAdapter.java create mode 100644 app/src/main/java/com/cappielloantonio/play/database/dao/ServerDao.java create mode 100644 app/src/main/java/com/cappielloantonio/play/model/Server.java create mode 100644 app/src/main/java/com/cappielloantonio/play/repository/ServerRepository.java create mode 100644 app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/ServerSignupDialog.java create mode 100644 app/src/main/java/com/cappielloantonio/play/viewmodel/LoginViewModel.java create mode 100644 app/src/main/res/drawable-v24/ic_add.xml create mode 100644 app/src/main/res/layout/dialog_server_signup.xml create mode 100644 app/src/main/res/layout/item_login_server.xml create mode 100644 app/src/main/res/menu/login_page_menu.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 788713e1..9a7274f1 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,20 +3,30 @@ diff --git a/app/src/main/java/com/cappielloantonio/play/adapter/ServerAdapter.java b/app/src/main/java/com/cappielloantonio/play/adapter/ServerAdapter.java new file mode 100644 index 00000000..9bb73413 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/adapter/ServerAdapter.java @@ -0,0 +1,127 @@ +package com.cappielloantonio.play.adapter; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.fragment.app.FragmentManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.cappielloantonio.play.App; +import com.cappielloantonio.play.R; +import com.cappielloantonio.play.interfaces.SystemCallback; +import com.cappielloantonio.play.model.Server; +import com.cappielloantonio.play.model.Song; +import com.cappielloantonio.play.repository.SystemRepository; +import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.PreferenceUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class ServerAdapter extends RecyclerView.Adapter { + private static final String TAG = "ServerAdapter"; + + private List servers; + private final LayoutInflater mInflater; + private MainActivity mainActivity; + private Context context; + + public ServerAdapter(MainActivity mainActivity, Context context) { + this.mInflater = LayoutInflater.from(context); + this.servers = new ArrayList<>(); + this.mainActivity = mainActivity; + this.context = context; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = mInflater.inflate(R.layout.item_login_server, parent, false); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + Server server = servers.get(position); + + holder.serverName.setText(server.getServerName()); + holder.serverAddress.setText(server.getAddress()); + } + + @Override + public int getItemCount() { + return servers.size(); + } + + public List getItems() { + return this.servers; + } + + public void setItems(List servers) { + this.servers = servers; + notifyDataSetChanged(); + } + + public Server getItem(int id) { + return servers.get(id); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + TextView serverName; + TextView serverAddress; + + ViewHolder(View itemView) { + super(itemView); + + serverName = itemView.findViewById(R.id.server_name_text_view); + serverAddress = itemView.findViewById(R.id.server_address_text_view); + + itemView.setOnClickListener(this); + + serverName.setSelected(true); + } + + @Override + public void onClick(View view) { + Server server = servers.get(getBindingAdapterPosition()); + saveServerPreference(server.getAddress(), server.getUsername(), server.getToken(), server.getSalt()); + + SystemRepository systemRepository = new SystemRepository(App.getInstance()); + systemRepository.checkUserCredential(new SystemCallback() { + @Override + public void onError(Exception exception) { + Toast.makeText(context, exception.getMessage(), Toast.LENGTH_SHORT).show(); + } + + @Override + public void onSuccess(String token, String salt) { + saveServerPreference(null, null, token, salt); + enter(); + } + }); + } + + private void enter() { + mainActivity.goFromLogin(); + } + + private void saveServerPreference(String server, String user, String token, String salt) { + if (user != null) PreferenceUtil.getInstance(context).setUser(user); + if (server != null) PreferenceUtil.getInstance(context).setServer(server); + + if (token != null && salt != null) { + PreferenceUtil.getInstance(context).setToken(token); + PreferenceUtil.getInstance(context).setSalt(salt); + PreferenceUtil.getInstance(context).setServerId(servers.get(getBindingAdapterPosition()).getServerId()); + + return; + } + + App.getSubsonicClientInstance(context, true); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/database/AppDatabase.java b/app/src/main/java/com/cappielloantonio/play/database/AppDatabase.java index 1328525e..e50be026 100644 --- a/app/src/main/java/com/cappielloantonio/play/database/AppDatabase.java +++ b/app/src/main/java/com/cappielloantonio/play/database/AppDatabase.java @@ -9,11 +9,13 @@ import androidx.room.RoomDatabase; import com.cappielloantonio.play.database.dao.DownloadDao; import com.cappielloantonio.play.database.dao.QueueDao; import com.cappielloantonio.play.database.dao.RecentSearchDao; +import com.cappielloantonio.play.database.dao.ServerDao; import com.cappielloantonio.play.model.Download; import com.cappielloantonio.play.model.Queue; import com.cappielloantonio.play.model.RecentSearch; +import com.cappielloantonio.play.model.Server; -@Database(entities = {Queue.class, RecentSearch.class, Download.class}, version = 5, exportSchema = false) +@Database(entities = {Queue.class, Server.class, RecentSearch.class, Download.class}, version = 9, exportSchema = false) public abstract class AppDatabase extends RoomDatabase { private static final String TAG = "AppDatabase"; @@ -31,6 +33,8 @@ public abstract class AppDatabase extends RoomDatabase { public abstract QueueDao queueDao(); + public abstract ServerDao serverDao(); + public abstract RecentSearchDao recentSearchDao(); public abstract DownloadDao downloadDao(); diff --git a/app/src/main/java/com/cappielloantonio/play/database/dao/DownloadDao.java b/app/src/main/java/com/cappielloantonio/play/database/dao/DownloadDao.java index 47d2276b..5659a061 100644 --- a/app/src/main/java/com/cappielloantonio/play/database/dao/DownloadDao.java +++ b/app/src/main/java/com/cappielloantonio/play/database/dao/DownloadDao.java @@ -15,8 +15,8 @@ public interface DownloadDao { @Query("SELECT * FROM download") List getAll(); - @Query("SELECT * FROM download LIMIT :size") - LiveData> getSample(int size); + @Query("SELECT * FROM download WHERE server=:server LIMIT :size") + LiveData> getSample(int size, String server); @Insert(onConflict = OnConflictStrategy.REPLACE) void insert(Download download); @@ -27,6 +27,6 @@ public interface DownloadDao { @Query("DELETE FROM download WHERE id = :id") void delete(String id); - @Query("DELETE FROM download") - void deleteAll(); + @Query("DELETE FROM download WHERE server=:server") + void deleteAll(String server); } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/database/dao/QueueDao.java b/app/src/main/java/com/cappielloantonio/play/database/dao/QueueDao.java index 7f2986f7..75da98b1 100644 --- a/app/src/main/java/com/cappielloantonio/play/database/dao/QueueDao.java +++ b/app/src/main/java/com/cappielloantonio/play/database/dao/QueueDao.java @@ -12,20 +12,20 @@ import java.util.List; @Dao public interface QueueDao { - @Query("SELECT * FROM queue") - LiveData> getAll(); + @Query("SELECT * FROM queue WHERE server = :server") + LiveData> getAll(String server); - @Query("SELECT * FROM queue") - List getAllSimple(); + @Query("SELECT * FROM queue WHERE server = :server") + List getAllSimple(String server); @Insert(onConflict = OnConflictStrategy.REPLACE) void insertAll(List songQueueObject); - @Query("DELETE FROM queue WHERE queue.track_order = :position") - void deleteByPosition(int position); + @Query("DELETE FROM queue WHERE queue.track_order = :position AND server = :server") + void deleteByPosition(int position, String server); - @Query("DELETE FROM queue") - void deleteAll(); + @Query("DELETE FROM queue WHERE server = :server") + void deleteAll(String server); @Query("SELECT COUNT(*) FROM queue;") int count(); diff --git a/app/src/main/java/com/cappielloantonio/play/database/dao/RecentSearchDao.java b/app/src/main/java/com/cappielloantonio/play/database/dao/RecentSearchDao.java index 34bf58c0..ca8dea58 100644 --- a/app/src/main/java/com/cappielloantonio/play/database/dao/RecentSearchDao.java +++ b/app/src/main/java/com/cappielloantonio/play/database/dao/RecentSearchDao.java @@ -20,7 +20,4 @@ public interface RecentSearchDao { @Delete void delete(RecentSearch search); - - @Query("DELETE FROM recent_search") - void deleteAll(); } \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/database/dao/ServerDao.java b/app/src/main/java/com/cappielloantonio/play/database/dao/ServerDao.java new file mode 100644 index 00000000..cff8d416 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/database/dao/ServerDao.java @@ -0,0 +1,25 @@ +package com.cappielloantonio.play.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.play.model.Queue; +import com.cappielloantonio.play.model.Server; + +import java.util.List; + +@Dao +public interface ServerDao { + @Query("SELECT * FROM server") + LiveData> getAll(); + + @Insert(onConflict = OnConflictStrategy.REPLACE) + void insert(Server server); + + @Delete + void delete(Server server); +} \ No newline at end of file diff --git a/app/src/main/java/com/cappielloantonio/play/model/Album.java b/app/src/main/java/com/cappielloantonio/play/model/Album.java index 7b6323b5..0cd539ce 100644 --- a/app/src/main/java/com/cappielloantonio/play/model/Album.java +++ b/app/src/main/java/com/cappielloantonio/play/model/Album.java @@ -34,7 +34,7 @@ public class Album implements Parcelable { public Album(AlbumID3 albumID3) { this.id = albumID3.getId(); this.title = albumID3.getName(); - this.year = albumID3.getYear(); + this.year = albumID3.getYear() != null ? albumID3.getYear() : 0; this.artistId = albumID3.getArtistId(); this.artistName = albumID3.getArtist(); this.primary = albumID3.getCoverArtId(); @@ -44,7 +44,7 @@ public class Album implements Parcelable { public Album(AlbumWithSongsID3 albumWithSongsID3) { this.id = albumWithSongsID3.getId(); this.title = albumWithSongsID3.getName(); - this.year = albumWithSongsID3.getYear(); + this.year = albumWithSongsID3.getYear() != null ? albumWithSongsID3.getYear() : 0; this.artistId = albumWithSongsID3.getArtistId(); this.artistName = albumWithSongsID3.getArtist(); this.primary = albumWithSongsID3.getCoverArtId(); diff --git a/app/src/main/java/com/cappielloantonio/play/model/Download.java b/app/src/main/java/com/cappielloantonio/play/model/Download.java index ad5523c0..de404e81 100644 --- a/app/src/main/java/com/cappielloantonio/play/model/Download.java +++ b/app/src/main/java/com/cappielloantonio/play/model/Download.java @@ -33,7 +33,10 @@ public class Download { @ColumnInfo(name = "duration") private long duration; - public Download(String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration) { + @ColumnInfo(name = "server") + private String server; + + public Download(String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration, String server) { this.songID = songID; this.title = title; this.albumId = albumId; @@ -42,6 +45,7 @@ public class Download { this.artistName = artistName; this.primary = primary; this.duration = duration; + this.server = server; } public Download(Song song) { @@ -118,4 +122,12 @@ public class Download { public void setDuration(long duration) { this.duration = duration; } + + public String getServer() { + return server; + } + + public void setServer(String server) { + this.server = server; + } } diff --git a/app/src/main/java/com/cappielloantonio/play/model/Queue.java b/app/src/main/java/com/cappielloantonio/play/model/Queue.java index 319d3e89..abe860c8 100644 --- a/app/src/main/java/com/cappielloantonio/play/model/Queue.java +++ b/app/src/main/java/com/cappielloantonio/play/model/Queue.java @@ -36,7 +36,10 @@ public class Queue { @ColumnInfo(name = "duration") private long duration; - public Queue(int trackOrder, String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration) { + @ColumnInfo(name = "server") + private String server; + + public Queue(int trackOrder, String songID, String title, String albumId, String albumName, String artistId, String artistName, String primary, long duration, String server) { this.trackOrder = trackOrder; this.songID = songID; this.title = title; @@ -46,6 +49,7 @@ public class Queue { this.artistName = artistName; this.primary = primary; this.duration = duration; + this.server = server; } public int getTrackOrder() { @@ -119,4 +123,12 @@ public class Queue { public void setDuration(long duration) { this.duration = duration; } + + public String getServer() { + return server; + } + + public void setServer(String server) { + this.server = server; + } } diff --git a/app/src/main/java/com/cappielloantonio/play/model/RecentSearch.java b/app/src/main/java/com/cappielloantonio/play/model/RecentSearch.java index 372e2d7b..d59f96f4 100644 --- a/app/src/main/java/com/cappielloantonio/play/model/RecentSearch.java +++ b/app/src/main/java/com/cappielloantonio/play/model/RecentSearch.java @@ -12,7 +12,7 @@ public class RecentSearch { @ColumnInfo(name = "search") private String search; - public RecentSearch(String search) { + public RecentSearch(@NonNull String search) { this.search = search; } @@ -21,7 +21,7 @@ public class RecentSearch { return search; } - public void setSearch(String search) { + public void setSearch(@NonNull String search) { this.search = search; } } diff --git a/app/src/main/java/com/cappielloantonio/play/model/Server.java b/app/src/main/java/com/cappielloantonio/play/model/Server.java new file mode 100644 index 00000000..6dce85cd --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/model/Server.java @@ -0,0 +1,99 @@ +package com.cappielloantonio.play.model; + +import androidx.annotation.NonNull; +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +@Entity(tableName = "server") +public class Server { + @NonNull + @PrimaryKey + @ColumnInfo(name = "id") + private String serverId; + + @ColumnInfo(name = "server_name") + private String serverName; + + @ColumnInfo(name = "username") + private String username; + + @ColumnInfo(name = "address") + private String address; + + @ColumnInfo(name = "token") + private String token; + + @ColumnInfo(name = "salt") + private String salt; + + @ColumnInfo(name = "timestamp") + private long timestamp; + + public Server(@NonNull String serverId, String serverName, String username, String address, String token, String salt, long timestamp) { + this.serverId = serverId; + this.serverName = serverName; + this.username = username; + this.address = address; + this.token = token; + this.salt = salt; + this.timestamp = timestamp; + } + + @NonNull + public String getServerId() { + return serverId; + } + + public void setServerId(@NonNull String serverId) { + this.serverId = serverId; + } + + public String getServerName() { + return serverName; + } + + public void setServerName(String serverName) { + this.serverName = serverName; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getAddress() { + return address; + } + + public void setAddress(String address) { + this.address = address; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public String getSalt() { + return salt; + } + + public void setSalt(String salt) { + this.salt = salt; + } + + public long getTimestamp() { + return timestamp; + } + + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/repository/AlbumRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/AlbumRepository.java index b4f0b303..e6509743 100644 --- a/app/src/main/java/com/cappielloantonio/play/repository/AlbumRepository.java +++ b/app/src/main/java/com/cappielloantonio/play/repository/AlbumRepository.java @@ -275,17 +275,19 @@ public class AlbumRepository { getFirstAlbum(first -> { getLastAlbum(last -> { - List decadeList = new ArrayList(); + if(first != -1 && last != -1) { + List decadeList = new ArrayList(); - int startDecade = first - (first % 10); - int lastDecade = last - (last % 10); + int startDecade = first - (first % 10); + int lastDecade = last - (last % 10); - while (startDecade <= lastDecade) { - decadeList.add(startDecade); - startDecade = startDecade + 10; + while (startDecade <= lastDecade) { + decadeList.add(startDecade); + startDecade = startDecade + 10; + } + + decades.setValue(decadeList); } - - decades.setValue(decadeList); }); }); @@ -295,7 +297,7 @@ public class AlbumRepository { private void getFirstAlbum(DecadesCallback callback) { App.getSubsonicClientInstance(application, false) .getAlbumSongListClient() - .getAlbumList2("byYear", 1, 0, 0, Calendar.getInstance().get(Calendar.YEAR)) + .getAlbumList2("byYear", 1, 0, 1900, Calendar.getInstance().get(Calendar.YEAR)) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { @@ -308,7 +310,7 @@ public class AlbumRepository { @Override public void onFailure(Call call, Throwable t) { - + callback.onLoadYear(-1); } }); } @@ -316,20 +318,23 @@ public class AlbumRepository { private void getLastAlbum(DecadesCallback callback) { App.getSubsonicClientInstance(application, false) .getAlbumSongListClient() - .getAlbumList2("byYear", 1, 0, Calendar.getInstance().get(Calendar.YEAR), 0) + .getAlbumList2("byYear", 1, 0, Calendar.getInstance().get(Calendar.YEAR), 1900) .enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { if (response.body().getStatus().getValue().equals(ResponseStatus.OK)) { - if(response.body().getAlbumList2().getAlbums().get(0) != null){ + if(response.body().getAlbumList2().getAlbums().size() > 0 && response.body().getAlbumList2().getAlbums().get(0) != null){ callback.onLoadYear(response.body().getAlbumList2().getAlbums().get(0).getYear()); } + else { + callback.onLoadYear(-1); + } } } @Override public void onFailure(Call call, Throwable t) { - + callback.onLoadYear(-1); } }); } diff --git a/app/src/main/java/com/cappielloantonio/play/repository/DownloadRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/DownloadRepository.java index 1c64e5e9..d02fddd9 100644 --- a/app/src/main/java/com/cappielloantonio/play/repository/DownloadRepository.java +++ b/app/src/main/java/com/cappielloantonio/play/repository/DownloadRepository.java @@ -5,9 +5,11 @@ import android.app.Application; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; +import com.cappielloantonio.play.App; import com.cappielloantonio.play.database.AppDatabase; import com.cappielloantonio.play.database.dao.DownloadDao; import com.cappielloantonio.play.model.Download; +import com.cappielloantonio.play.util.PreferenceUtil; import java.util.ArrayList; import java.util.List; @@ -60,7 +62,7 @@ public class DownloadRepository { } public LiveData> getLiveDownloadSample(int size) { - listLiveDownloadSample = downloadDao.getSample(size); + listLiveDownloadSample = downloadDao.getSample(size, PreferenceUtil.getInstance(App.getInstance()).getServerId()); return listLiveDownloadSample; } @@ -121,7 +123,7 @@ public class DownloadRepository { @Override public void run() { - downloadDao.deleteAll(); + downloadDao.deleteAll(PreferenceUtil.getInstance(App.getInstance()).getServerId()); } } diff --git a/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java index 64d06730..61327354 100644 --- a/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java +++ b/app/src/main/java/com/cappielloantonio/play/repository/QueueRepository.java @@ -4,11 +4,13 @@ import android.app.Application; import androidx.lifecycle.LiveData; +import com.cappielloantonio.play.App; import com.cappielloantonio.play.database.AppDatabase; import com.cappielloantonio.play.database.dao.QueueDao; import com.cappielloantonio.play.model.Queue; import com.cappielloantonio.play.model.Song; import com.cappielloantonio.play.util.MappingUtil; +import com.cappielloantonio.play.util.PreferenceUtil; import com.cappielloantonio.play.util.QueueUtil; import java.util.ArrayList; @@ -26,7 +28,7 @@ public class QueueRepository { } public LiveData> getLiveQueue() { - listLiveQueue = queueDao.getAll(); + listLiveQueue = queueDao.getAll(PreferenceUtil.getInstance(App.getInstance()).getServerId()); return listLiveQueue; } @@ -94,7 +96,7 @@ public class QueueRepository { @Override public void run() { - songs = MappingUtil.mapQueue(queueDao.getAllSimple()); + songs = MappingUtil.mapQueue(queueDao.getAllSimple(PreferenceUtil.getInstance(App.getInstance()).getServerId())); } public List getSongs() { @@ -113,7 +115,7 @@ public class QueueRepository { @Override public void run() { - queueDao.insertAll(QueueUtil.getQueueElementsFromSongs(songs)); + queueDao.insertAll(QueueUtil.getQueueElementsFromSongs(songs, PreferenceUtil.getInstance(App.getInstance()).getServerId())); } } @@ -128,7 +130,7 @@ public class QueueRepository { @Override public void run() { - queueDao.deleteByPosition(position); + queueDao.deleteByPosition(position, PreferenceUtil.getInstance(App.getInstance()).getServerId()); } } @@ -141,7 +143,7 @@ public class QueueRepository { @Override public void run() { - queueDao.deleteAll(); + queueDao.deleteAll(PreferenceUtil.getInstance(App.getInstance()).getServerId()); } } diff --git a/app/src/main/java/com/cappielloantonio/play/repository/ServerRepository.java b/app/src/main/java/com/cappielloantonio/play/repository/ServerRepository.java new file mode 100644 index 00000000..32c511a4 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/repository/ServerRepository.java @@ -0,0 +1,70 @@ +package com.cappielloantonio.play.repository; + +import android.app.Application; + +import androidx.lifecycle.LiveData; + +import com.cappielloantonio.play.database.AppDatabase; +import com.cappielloantonio.play.database.dao.ServerDao; +import com.cappielloantonio.play.model.Server; + +import java.util.List; + +public class ServerRepository { + private static final String TAG = "QueueRepository"; + + private ServerDao serverDao; + private LiveData> listLiveServer; + + public ServerRepository(Application application) { + AppDatabase database = AppDatabase.getInstance(application); + serverDao = database.serverDao(); + } + + public LiveData> getLiveServer() { + listLiveServer = serverDao.getAll(); + return listLiveServer; + } + + 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 ServerDao serverDao; + private 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 ServerDao serverDao; + private Server server; + + public DeleteThreadSafe(ServerDao serverDao, Server server) { + this.serverDao = serverDao; + this.server = server; + } + + @Override + public void run() { + serverDao.delete(server); + } + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java b/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java index 30cb5c7a..e9ec2d8f 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/activity/MainActivity.java @@ -179,8 +179,11 @@ public class MainActivity extends BaseActivity { // NAVIGATION public void goToLogin() { - if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) + if (Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.landingFragment) { navController.navigate(R.id.action_landingFragment_to_loginFragment); + } else if(Objects.requireNonNull(navController.getCurrentDestination()).getId() == R.id.settingsFragment) { + navController.navigate(R.id.action_settingsFragment_to_loginFragment); + } } public void goToHome() { diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/LoginFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/LoginFragment.java index d3a9e94d..d3e09a00 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/LoginFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/LoginFragment.java @@ -1,44 +1,64 @@ package com.cappielloantonio.play.ui.fragment; import android.os.Bundle; -import android.text.TextUtils; -import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; -import com.cappielloantonio.play.App; +import com.cappielloantonio.play.R; +import com.cappielloantonio.play.adapter.ServerAdapter; import com.cappielloantonio.play.databinding.FragmentLoginBinding; -import com.cappielloantonio.play.interfaces.SystemCallback; -import com.cappielloantonio.play.repository.SystemRepository; +import com.cappielloantonio.play.service.MusicPlayerRemote; import com.cappielloantonio.play.ui.activity.MainActivity; -import com.cappielloantonio.play.util.PreferenceUtil; +import com.cappielloantonio.play.ui.fragment.dialog.ServerSignupDialog; +import com.cappielloantonio.play.viewmodel.LoginViewModel; + +import java.util.Collections; public class LoginFragment extends Fragment { private static final String TAG = "LoginFragment"; private FragmentLoginBinding bind; private MainActivity activity; + private LoginViewModel loginViewModel; - private String username; - private String password; - private String server; + private ServerAdapter serverAdapter; - private SystemRepository systemRepository; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + inflater.inflate(R.menu.login_page_menu, menu); + } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { activity = (MainActivity) getActivity(); + loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class); bind = FragmentLoginBinding.inflate(inflater, container, false); View view = bind.getRoot(); - init(); + + initAppBar(); + initServerListView(); return view; } @@ -49,67 +69,75 @@ public class LoginFragment extends Fragment { bind = null; } - private void init() { - systemRepository = new SystemRepository(App.getInstance()); + private void initAppBar() { + activity.setSupportActionBar(bind.toolbar); - bind.loginButton.setOnClickListener(v -> { - if (validateInput()) { - saveServerPreference(server, username, password, null, null); - authenticate(); + bind.appBarLayout.addOnOffsetChangedListener((appBarLayout, verticalOffset) -> { + if ((bind.serverInfoSector.getHeight() + verticalOffset) < (2 * ViewCompat.getMinimumHeight(bind.toolbar))) { + bind.toolbar.setTitle("Subsonic servers"); + } else { + bind.toolbar.setTitle(""); } }); } - private boolean validateInput() { - username = bind.usernameTextView.getText().toString().trim(); - password = bind.passwordTextView.getText().toString(); - server = bind.serverTextView.getText().toString().trim(); + private void initServerListView() { + bind.serverListRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext())); + bind.serverListRecyclerView.setHasFixedSize(true); - if (TextUtils.isEmpty(username)) { - Toast.makeText(requireContext(), "Empty username", Toast.LENGTH_SHORT).show(); - return false; - } - - if (TextUtils.isEmpty(server)) { - Toast.makeText(requireContext(), "Empty server url", Toast.LENGTH_SHORT).show(); - return false; - } - - return true; - } - - private void authenticate() { - systemRepository.checkUserCredential(new SystemCallback() { - @Override - public void onError(Exception exception) { - Log.e(TAG, exception.getMessage()); - Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_LONG).show(); + serverAdapter = new ServerAdapter(activity, requireContext()); + bind.serverListRecyclerView.setAdapter(serverAdapter); + loginViewModel.getServerList().observe(requireActivity(), servers -> { + if(servers.size() > 0) { + if (bind != null) bind.noServerAddedTextView.setVisibility(View.GONE); + if (bind != null) bind.serverListRecyclerView.setVisibility(View.VISIBLE); + serverAdapter.setItems(servers); } - - @Override - public void onSuccess(String token, String salt) { - saveServerPreference(null, null, null, token, salt); - enter(); + else { + if (bind != null) bind.noServerAddedTextView.setVisibility(View.VISIBLE); + if (bind != null) bind.serverListRecyclerView.setVisibility(View.GONE); } }); + + new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.LEFT) { + int originalPosition = -1; + int fromPosition = -1; + int toPosition = -1; + + @Override + public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { + if (originalPosition == -1) + originalPosition = viewHolder.getBindingAdapterPosition(); + + fromPosition = viewHolder.getBindingAdapterPosition(); + toPosition = target.getBindingAdapterPosition(); + + Collections.swap(serverAdapter.getItems(), fromPosition, toPosition); + recyclerView.getAdapter().notifyItemMoved(fromPosition, toPosition); + + return false; + } + + @Override + public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { + loginViewModel.deleteServer(serverAdapter.getItem(viewHolder.getBindingAdapterPosition())); + bind.serverListRecyclerView.getAdapter().notifyItemRemoved(viewHolder.getBindingAdapterPosition()); + } + } + ).attachToRecyclerView(bind.serverListRecyclerView); } - private void enter() { - activity.goFromLogin(); - } - - private void saveServerPreference(String server, String user, String password, String token, String salt) { - if (user != null) PreferenceUtil.getInstance(requireContext()).setUser(user); - if (server != null) PreferenceUtil.getInstance(requireContext()).setServer(server); - if (password != null) PreferenceUtil.getInstance(requireContext()).setPassword(password); - - if (token != null && salt != null) { - PreferenceUtil.getInstance(requireContext()).setPassword(null); - PreferenceUtil.getInstance(requireContext()).setToken(token); - PreferenceUtil.getInstance(requireContext()).setSalt(salt); - return; + @Override + public boolean onOptionsItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.action_add: + ServerSignupDialog dialog = new ServerSignupDialog(); + dialog.show(activity.getSupportFragmentManager(), null); + return true; + default: + break; } - App.getSubsonicClientInstance(requireContext(), true); + return false; } } diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/SettingsFragment.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/SettingsFragment.java index 10039ca5..45f2880e 100644 --- a/app/src/main/java/com/cappielloantonio/play/ui/fragment/SettingsFragment.java +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/SettingsFragment.java @@ -6,11 +6,13 @@ import android.view.View; import android.view.ViewGroup; import androidx.preference.ListPreference; +import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import com.cappielloantonio.play.R; import com.cappielloantonio.play.helper.ThemeHelper; import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.PreferenceUtil; public class SettingsFragment extends PreferenceFragmentCompat { private static final String TAG = "SettingsFragment"; @@ -36,6 +38,24 @@ public class SettingsFragment extends PreferenceFragmentCompat { activity.setBottomNavigationBarVisibility(false); } + @Override + public void onResume() { + super.onResume(); + + findPreference("logout").setOnPreferenceClickListener(preference -> { + PreferenceUtil.getInstance(requireContext()).setUser(null); + PreferenceUtil.getInstance(requireContext()).setServer(null); + PreferenceUtil.getInstance(requireContext()).setPassword(null); + PreferenceUtil.getInstance(requireContext()).setToken(null); + PreferenceUtil.getInstance(requireContext()).setSalt(null); + PreferenceUtil.getInstance(requireContext()).setServerId(null); + + activity.goToLogin(); + + return true; + }); + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { setPreferencesFromResource(R.xml.global_preferences, rootKey); diff --git a/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/ServerSignupDialog.java b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/ServerSignupDialog.java new file mode 100644 index 00000000..9a550008 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/ui/fragment/dialog/ServerSignupDialog.java @@ -0,0 +1,142 @@ +package com.cappielloantonio.play.ui.fragment.dialog; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import android.view.LayoutInflater; +import android.widget.Toast; + +import androidx.fragment.app.DialogFragment; +import androidx.lifecycle.ViewModelProvider; + +import com.cappielloantonio.play.App; +import com.cappielloantonio.play.R; +import com.cappielloantonio.play.databinding.DialogServerSignupBinding; +import com.cappielloantonio.play.interfaces.SystemCallback; +import com.cappielloantonio.play.model.Server; +import com.cappielloantonio.play.repository.SystemRepository; +import com.cappielloantonio.play.ui.activity.MainActivity; +import com.cappielloantonio.play.util.PreferenceUtil; +import com.cappielloantonio.play.viewmodel.LoginViewModel; + +import java.util.Objects; +import java.util.UUID; + +public class ServerSignupDialog extends DialogFragment { + private static final String TAG = "ServerSignupDialog"; + + private DialogServerSignupBinding bind; + private MainActivity activity; + private Context context; + private LoginViewModel loginViewModel; + + private SystemRepository systemRepository; + + private String serverName; + private String username; + private String password; + private String server; + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + activity = (MainActivity) getActivity(); + context = requireContext(); + + loginViewModel = new ViewModelProvider(requireActivity()).get(LoginViewModel.class); + systemRepository = new SystemRepository(App.getInstance()); + + bind = DialogServerSignupBinding.inflate(LayoutInflater.from(requireContext())); + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + + builder.setView(bind.getRoot()) + .setTitle("Add server") + .setPositiveButton("Enter", (dialog, id) -> { }) + .setNegativeButton("Cancel", (dialog, id) -> dialog.cancel()); + + return builder.create(); + } + + @Override + public void onStart() { + super.onStart(); + ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getResources().getColor(R.color.colorAccent, null)); + ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(getResources().getColor(R.color.colorAccent, null)); + + ((AlertDialog) Objects.requireNonNull(getDialog())).getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v -> { + if (validateInput()) { + saveServerPreference(server, username, password, null, null); + authenticate(); + ((AlertDialog) Objects.requireNonNull(getDialog())).dismiss(); + } + }); + } + + private boolean validateInput() { + serverName = bind.serverNameTextView.getText().toString().trim(); + username = bind.usernameTextView.getText().toString().trim(); + password = bind.passwordTextView.getText().toString(); + server = bind.serverTextView.getText().toString().trim(); + + if (TextUtils.isEmpty(serverName)) { + bind.serverNameTextView.setError("Required"); + return false; + } + + if (TextUtils.isEmpty(username)) { + bind.usernameTextView.setError("Required"); + return false; + } + + if (TextUtils.isEmpty(server)) { + bind.serverTextView.setError("Required"); + return false; + } + + return true; + } + + private void authenticate() { + systemRepository.checkUserCredential(new SystemCallback() { + @Override + public void onError(Exception exception) { + Log.e(TAG, exception.getMessage()); + Toast.makeText(requireContext(), exception.getMessage(), Toast.LENGTH_LONG).show(); + } + + @Override + public void onSuccess(String token, String salt) { + saveServerPreference(null, null, null, token, salt); + enter(); + } + }); + } + + private void enter() { + activity.goFromLogin(); + } + + private void saveServerPreference(String server, String user, String password, String token, String salt) { + if (user != null) PreferenceUtil.getInstance(context).setUser(user); + if (server != null) PreferenceUtil.getInstance(context).setServer(server); + if (password != null) PreferenceUtil.getInstance(context).setPassword(password); + + if (token != null && salt != null) { + String serverID = UUID.randomUUID().toString(); + + PreferenceUtil.getInstance(context).setPassword(null); + PreferenceUtil.getInstance(context).setToken(token); + PreferenceUtil.getInstance(context).setSalt(salt); + PreferenceUtil.getInstance(context).setServerId(serverID); + + loginViewModel.addServer(new Server(serverID, this.serverName, this.username, this.server, token, salt, System.currentTimeMillis())); + + return; + } + + App.getSubsonicClientInstance(requireContext(), true); + } +} diff --git a/app/src/main/java/com/cappielloantonio/play/util/PreferenceUtil.java b/app/src/main/java/com/cappielloantonio/play/util/PreferenceUtil.java index 0e691b45..0a30a2ea 100644 --- a/app/src/main/java/com/cappielloantonio/play/util/PreferenceUtil.java +++ b/app/src/main/java/com/cappielloantonio/play/util/PreferenceUtil.java @@ -15,6 +15,7 @@ public class PreferenceUtil { public static final String PASSWORD = "password"; public static final String TOKEN = "token"; public static final String SALT = "salt"; + public static final String SERVER_ID = "server_id"; public static final String POSITION = "position"; public static final String PROGRESS = "progress"; public static final String IMAGE_CACHE_SIZE = "image_cache_size"; @@ -89,6 +90,16 @@ public class PreferenceUtil { editor.apply(); } + public String getServerId() { + return mPreferences.getString(SERVER_ID, null); + } + + public void setServerId(String serverId) { + final SharedPreferences.Editor editor = mPreferences.edit(); + editor.putString(SERVER_ID, serverId); + editor.apply(); + } + public int getPosition() { return mPreferences.getInt(POSITION, -1); } diff --git a/app/src/main/java/com/cappielloantonio/play/util/QueueUtil.java b/app/src/main/java/com/cappielloantonio/play/util/QueueUtil.java index f9e01252..dc7302f6 100644 --- a/app/src/main/java/com/cappielloantonio/play/util/QueueUtil.java +++ b/app/src/main/java/com/cappielloantonio/play/util/QueueUtil.java @@ -9,12 +9,12 @@ import java.util.List; public class QueueUtil { private static final String TAG = "QueueUtil"; - public static List getQueueElementsFromSongs(List songs) { + public static List getQueueElementsFromSongs(List songs, String serverID) { int counter = 0; List queue = new ArrayList<>(); for (Song song : songs) { - queue.add(new Queue(counter, song.getId(), song.getTitle(), song.getAlbumId(), song.getAlbumName(), song.getArtistId(), song.getArtistName(), song.getPrimary(), song.getDuration())); + queue.add(new Queue(counter, song.getId(), song.getTitle(), song.getAlbumId(), song.getAlbumName(), song.getArtistId(), song.getArtistName(), song.getPrimary(), song.getDuration(), serverID)); counter++; } diff --git a/app/src/main/java/com/cappielloantonio/play/viewmodel/LoginViewModel.java b/app/src/main/java/com/cappielloantonio/play/viewmodel/LoginViewModel.java new file mode 100644 index 00000000..c091b631 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/play/viewmodel/LoginViewModel.java @@ -0,0 +1,48 @@ +package com.cappielloantonio.play.viewmodel; + +import android.app.Application; +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.cappielloantonio.play.App; +import com.cappielloantonio.play.interfaces.MediaCallback; +import com.cappielloantonio.play.model.Album; +import com.cappielloantonio.play.model.Server; +import com.cappielloantonio.play.repository.AlbumRepository; +import com.cappielloantonio.play.repository.ServerRepository; +import com.cappielloantonio.play.subsonic.models.AlbumID3; +import com.cappielloantonio.play.subsonic.models.ResponseStatus; +import com.cappielloantonio.play.subsonic.models.SubsonicResponse; +import com.cappielloantonio.play.util.MappingUtil; + +import java.util.ArrayList; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; + +public class LoginViewModel extends AndroidViewModel { + private ServerRepository serverRepository; + + public LoginViewModel(@NonNull Application application) { + super(application); + + serverRepository = new ServerRepository(application); + } + + public LiveData> getServerList() { + return serverRepository.getLiveServer(); + } + + public void addServer(Server server) { + serverRepository.insert(server); + } + + public void deleteServer(Server server) { + serverRepository.delete(server); + } +} diff --git a/app/src/main/res/drawable-v24/ic_add.xml b/app/src/main/res/drawable-v24/ic_add.xml new file mode 100644 index 00000000..57198ebb --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_add.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/dialog_server_signup.xml b/app/src/main/res/layout/dialog_server_signup.xml new file mode 100644 index 00000000..6fd24079 --- /dev/null +++ b/app/src/main/res/layout/dialog_server_signup.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login.xml b/app/src/main/res/layout/fragment_login.xml index 8e36b6b2..83477039 100644 --- a/app/src/main/res/layout/fragment_login.xml +++ b/app/src/main/res/layout/fragment_login.xml @@ -1,104 +1,70 @@ + android:orientation="vertical"> - + android:layout_height="?attr/actionBarSize" + app:layout_collapseMode="pin" /> - + + + app:elevation="0dp"> - + - + + + - - - + android:paddingStart="16dp" + android:paddingTop="16dp" + android:paddingEnd="16dp" + android:gravity="center" + android:text="No server added" + android:clipToPadding="false" + android:paddingBottom="@dimen/global_padding_bottom" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + android:visibility="gone"/> - - - - - - - - - -