From b3c418968d1b658d2650f2ef00feb27343e03164 Mon Sep 17 00:00:00 2001 From: Fawkes100 Date: Sun, 19 Jan 2025 09:39:02 +0100 Subject: [PATCH] ADD: Sync Created Songs with Server --- .../notevault/data/dao/SongDao.java | 10 +++ .../data/repositories/SongSyncRepository.java | 64 ++++++++++++++++++ .../notevault/network/sync/SongSyncAPI.java | 15 +++++ .../network/sync/SongSyncModule.java | 40 +++++++++++ .../network/sync/SongSyncService.java | 44 ++++++++++++ .../sync/models/BatchCreateResponse.java | 15 +++++ .../network/sync/models/CreateResponse.java | 22 ++++++ .../sync/models/SongCreateBatchRequest.java | 20 ++++++ .../sync/models/SongCreateRequest.java | 67 +++++++++++++++++++ .../notevault/ui/gallery/GalleryFragment.java | 34 ++++++++-- .../ui/gallery/GalleryViewModel.java | 27 ++++++-- app/src/main/res/layout/fragment_gallery.xml | 63 +++++++++++++---- 12 files changed, 399 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/models/BatchCreateResponse.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/models/CreateResponse.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateBatchRequest.java create mode 100644 app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateRequest.java diff --git a/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java b/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java index 2bb4809..e2debca 100644 --- a/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java +++ b/app/src/main/java/com/stormtales/notevault/data/dao/SongDao.java @@ -4,6 +4,7 @@ import androidx.lifecycle.LiveData; import androidx.room.*; import com.stormtales.notevault.data.entities.NoteSheet; import com.stormtales.notevault.data.entities.Song; +import com.stormtales.notevault.data.sync.SyncStatus; import java.util.List; @@ -29,4 +30,13 @@ public interface SongDao { @Query("SELECT localFileName FROM NoteSheet WHERE songID = :songID") List getNoteSheetFilesBySongID(int songID); + + @Query("SELECT * FROM Song WHERE syncStatus = :syncStatus") + List getSongsBySyncStatus(SyncStatus syncStatus); + + @Update + void updateSongs(List songs); + + @Query("SELECT * FROM Song WHERE localID IN (:localIDs)") + List getSongsByLocalIDs(List localIDs); } diff --git a/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java b/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java new file mode 100644 index 0000000..671372c --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/data/repositories/SongSyncRepository.java @@ -0,0 +1,64 @@ +package com.stormtales.notevault.data.repositories; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import com.stormtales.notevault.data.MusicDatabase; +import com.stormtales.notevault.data.dao.SongDao; +import com.stormtales.notevault.data.entities.Song; +import com.stormtales.notevault.data.sync.SyncStatus; +import com.stormtales.notevault.network.sync.models.BatchCreateResponse; +import com.stormtales.notevault.network.sync.models.CreateResponse; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; + +public class SongSyncRepository { + private SongDao songDao; + public SongSyncRepository(Context context) { + MusicDatabase database = MusicDatabase.getDatabase(context); + songDao = database.getSongTable(); + } + + public void loadCreatedSongs(LoadDataCallback> callback) { + Executors.newSingleThreadExecutor().execute(() -> { + List createdSongs = songDao.getSongsBySyncStatus(SyncStatus.CREATED); + Handler mainHandler = new Handler(Looper.getMainLooper()); + mainHandler.post(()-> callback.onResult(createdSongs)); + }); + } + + public void markCreatedSongsAsSynced(BatchCreateResponse createdSongs) { + Executors.newSingleThreadExecutor().execute(() -> { + List localIDs = createdSongs.getCreateResponses().stream().map(CreateResponse::getLocalID).collect(Collectors.toList()); + List requestedSongs = songDao.getSongsByLocalIDs(localIDs); + + for(Song song : requestedSongs) { + song.setSyncTime(LocalDateTime.now()); + song.setSyncStatus(SyncStatus.SYNCED); + + for(CreateResponse createResponse : createdSongs.getCreateResponses()) { + if(createResponse.getLocalID() == song.getLocalID()) { + song.setServerID(createResponse.getServerID()); + break; + } + } + } + songDao.updateSongs(requestedSongs); + }); + } + + public void markSongsAsSynced(List songs) { + for(Song song : songs) { + song.setSyncStatus(SyncStatus.SYNCED); + } + } + + public interface LoadDataCallback { + void onResult(T result); + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java new file mode 100644 index 0000000..85d0910 --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncAPI.java @@ -0,0 +1,15 @@ +package com.stormtales.notevault.network.sync; + +import com.stormtales.notevault.network.auth.LoginRequest; +import com.stormtales.notevault.network.auth.LoginResponse; +import com.stormtales.notevault.network.sync.models.BatchCreateResponse; +import com.stormtales.notevault.network.sync.models.SongCreateBatchRequest; +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface SongSyncAPI { + + @POST("/sync/songs/create") + Call syncCreatedSongs(@Body SongCreateBatchRequest songCreateBatchRequest); +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java new file mode 100644 index 0000000..f5c959f --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncModule.java @@ -0,0 +1,40 @@ +package com.stormtales.notevault.network.sync; + +import android.content.Context; +import com.stormtales.notevault.data.entities.Song; +import com.stormtales.notevault.data.repositories.SongRepository; +import com.stormtales.notevault.data.repositories.SongSyncRepository; +import com.stormtales.notevault.network.sync.models.BatchCreateResponse; +import com.stormtales.notevault.ui.gallery.GalleryViewModel; + +import java.util.List; + +public class SongSyncModule { + private SongRepository songRepository; + private SongSyncRepository songSyncRepository; + private SongSyncService songSyncService; + private GalleryViewModel syncViewModel; + + public SongSyncModule(Context context, GalleryViewModel syncViewModel) { + this.songRepository = new SongRepository(context); + this.songSyncRepository = new SongSyncRepository(context); + this.songSyncService = new SongSyncService(context); + this.syncViewModel = syncViewModel; + } + + public void syncCreatedSongs(FinishSongSyncingCallback callback) { + songSyncRepository.loadCreatedSongs(result -> { + songSyncService.syncCreatedSongs(result, new FinishSongSyncingCallback() { + @Override + public void finishSongSyncing(BatchCreateResponse response) { + songSyncRepository.markCreatedSongsAsSynced(response); + callback.finishSongSyncing(response); + } + }); + }); + } + + public interface FinishSongSyncingCallback { + void finishSongSyncing(BatchCreateResponse response); + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java new file mode 100644 index 0000000..35f279f --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/SongSyncService.java @@ -0,0 +1,44 @@ +package com.stormtales.notevault.network.sync; + +import android.content.Context; +import com.stormtales.notevault.data.entities.Song; +import com.stormtales.notevault.network.NetworkModule; +import com.stormtales.notevault.network.auth.AuthAPI; +import com.stormtales.notevault.network.sync.models.BatchCreateResponse; +import com.stormtales.notevault.network.sync.models.SongCreateBatchRequest; +import com.stormtales.notevault.network.sync.models.SongCreateRequest; +import com.stormtales.notevault.ui.gallery.GalleryViewModel; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +import java.util.ArrayList; +import java.util.List; + +public class SongSyncService { + private final SongSyncAPI songSyncAPI; + private GalleryViewModel syncStatusViewModel; + + public SongSyncService(Context context) { + songSyncAPI = NetworkModule.getRetrofitInstance(context).create(SongSyncAPI.class); + } + + public void syncCreatedSongs(List songs, SongSyncModule.FinishSongSyncingCallback callback) { + List createRequests = new ArrayList<>(); + for(Song song : songs) { + createRequests.add(new SongCreateRequest(song)); + } + SongCreateBatchRequest songCreateBatchRequest = new SongCreateBatchRequest(createRequests); + songSyncAPI.syncCreatedSongs(songCreateBatchRequest).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + callback.finishSongSyncing(response.body()); + } + + @Override + public void onFailure(Call call, Throwable throwable) { + + } + }); + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/BatchCreateResponse.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/BatchCreateResponse.java new file mode 100644 index 0000000..551d679 --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/BatchCreateResponse.java @@ -0,0 +1,15 @@ +package com.stormtales.notevault.network.sync.models; + +import java.util.List; + +public class BatchCreateResponse { + private List createResponses; + + public List getCreateResponses() { + return createResponses; + } + + public void setCreateResponses(List createResponses) { + this.createResponses = createResponses; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/CreateResponse.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/CreateResponse.java new file mode 100644 index 0000000..309c360 --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/CreateResponse.java @@ -0,0 +1,22 @@ +package com.stormtales.notevault.network.sync.models; + +public class CreateResponse { + private int localID; + private String serverID; + + public int getLocalID() { + return localID; + } + + public void setLocalID(int localID) { + this.localID = localID; + } + + public String getServerID() { + return serverID; + } + + public void setServerID(String serverID) { + this.serverID = serverID; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateBatchRequest.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateBatchRequest.java new file mode 100644 index 0000000..89924d1 --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateBatchRequest.java @@ -0,0 +1,20 @@ +package com.stormtales.notevault.network.sync.models; + +import java.util.List; + +public class SongCreateBatchRequest { + + private List songs; + + public SongCreateBatchRequest(List songs) { + this.songs = songs; + } + + public List getSongs() { + return songs; + } + + public void setSongs(List songs) { + this.songs = songs; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateRequest.java b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateRequest.java new file mode 100644 index 0000000..a5f74bb --- /dev/null +++ b/app/src/main/java/com/stormtales/notevault/network/sync/models/SongCreateRequest.java @@ -0,0 +1,67 @@ +package com.stormtales.notevault.network.sync.models; + +import com.stormtales.notevault.data.entities.Song; + +public class SongCreateRequest { + private int localID; + private String title; + private String composer; + private String genre; + private int year; + + public SongCreateRequest(int localID, String title, String composer, String genre, int year) { + this.localID = localID; + this.title = title; + this.composer = composer; + this.genre = genre; + this.year = year; + } + + public SongCreateRequest(Song song) { + this.localID = song.getLocalID(); + this.title = song.getTitle(); + this.composer = song.getComposer(); + this.genre = song.getGenre(); + this.year = song.getYear(); + } + + public int getLocalID() { + return localID; + } + + public void setLocalID(int localID) { + this.localID = localID; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getComposer() { + return composer; + } + + public void setComposer(String composer) { + this.composer = composer; + } + + public String getGenre() { + return genre; + } + + public void setGenre(String genre) { + this.genre = genre; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } +} diff --git a/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryFragment.java b/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryFragment.java index 582a46e..d080afb 100644 --- a/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryFragment.java +++ b/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryFragment.java @@ -4,26 +4,48 @@ import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; +import com.stormtales.notevault.data.repositories.SongRepository; import com.stormtales.notevault.databinding.FragmentGalleryBinding; +import com.stormtales.notevault.network.sync.SongSyncModule; +import com.stormtales.notevault.network.sync.SongSyncService; + public class GalleryFragment extends Fragment { + GalleryViewModel galleryViewModel; private FragmentGalleryBinding binding; + private ProgressBar progress_sync_created_songs; + private Button sync_created_songs_btn; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - GalleryViewModel galleryViewModel = - new ViewModelProvider(this).get(GalleryViewModel.class); + galleryViewModel = new ViewModelProvider(this).get(GalleryViewModel.class); binding = FragmentGalleryBinding.inflate(inflater, container, false); View root = binding.getRoot(); - final TextView textView = binding.textGallery; - galleryViewModel.getText().observe(getViewLifecycleOwner(), textView::setText); + progress_sync_created_songs = binding.progressSyncCreatedSongs; + sync_created_songs_btn = binding.syncCreatedBtn; + sync_created_songs_btn.setOnClickListener(v -> onSyncCreatedSongs()); + + galleryViewModel.setSongSyncModule(new SongSyncModule(getContext(), galleryViewModel)); + + galleryViewModel.getIsCreatedSongSyncing().observe(getViewLifecycleOwner(), isCreatedSyncinc -> { + if(isCreatedSyncinc) { + progress_sync_created_songs.setIndeterminate(true); + sync_created_songs_btn.setEnabled(false); + } else { + progress_sync_created_songs.setIndeterminate(false); + sync_created_songs_btn.setEnabled(true); + } + }); return root; } @@ -32,4 +54,8 @@ public class GalleryFragment extends Fragment { super.onDestroyView(); binding = null; } + + public void onSyncCreatedSongs() { + galleryViewModel.startCreateSongSyncing(); + } } \ No newline at end of file diff --git a/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryViewModel.java b/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryViewModel.java index cfb1d06..30151d3 100644 --- a/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryViewModel.java +++ b/app/src/main/java/com/stormtales/notevault/ui/gallery/GalleryViewModel.java @@ -3,17 +3,34 @@ package com.stormtales.notevault.ui.gallery; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import com.stormtales.notevault.data.repositories.SongRepository; +import com.stormtales.notevault.network.sync.SongSyncModule; public class GalleryViewModel extends ViewModel { - private final MutableLiveData mText; + private final MutableLiveData isCreatedSongSyncing; + private SongSyncModule songSyncModule; public GalleryViewModel() { - mText = new MutableLiveData<>(); - mText.setValue("This is gallery fragment"); + + isCreatedSongSyncing = new MutableLiveData<>(); + isCreatedSongSyncing.setValue(false); } - public LiveData getText() { - return mText; + public MutableLiveData getIsCreatedSongSyncing() { + return isCreatedSongSyncing; + } + + public void startCreateSongSyncing() { + if(this.songSyncModule == null) throw new RuntimeException("SongSyncModule is not initialized"); + this.isCreatedSongSyncing.setValue(true); + + this.songSyncModule.syncCreatedSongs(batchResponse -> { + this.isCreatedSongSyncing.setValue(false); + }); + } + + public void setSongSyncModule(SongSyncModule songSyncModule) { + this.songSyncModule = songSyncModule; } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_gallery.xml b/app/src/main/res/layout/fragment_gallery.xml index 82d5b10..be9fe77 100644 --- a/app/src/main/res/layout/fragment_gallery.xml +++ b/app/src/main/res/layout/fragment_gallery.xml @@ -6,18 +6,55 @@ android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.gallery.GalleryFragment"> - - + android:layout_height="match_parent"> + + + + + + + + + +