diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-10-18 20:19:13 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2020-10-18 20:19:13 +0300 |
commit | 6e336446afc3386b9bc4f6728a65ad87c032e461 (patch) | |
tree | 2034befc4fc30aebc1a9ab07e6728b1b99ffde72 /app/src/main/java | |
parent | f90d06ab3e97b13e13fcd5f17f195ce1133ae0ab (diff) |
Remove `localAccount` state from MainActivity and descendants
Diffstat (limited to 'app/src/main/java')
11 files changed, 299 insertions, 165 deletions
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java index b9a8e445..49bca9e1 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/AppendToNoteActivity.java @@ -8,6 +8,7 @@ import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; +import androidx.lifecycle.LiveData; import it.niedermann.owncloud.notes.main.MainActivity; import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; @@ -36,15 +37,22 @@ public class AppendToNoteActivity extends MainActivity { @Override public void onNoteClick(int position, View v) { if (receivedText != null && receivedText.length() > 0) { - final NoteWithCategory note = mainViewModel.getNoteWithCategory(localAccount.getId(), ((NoteWithCategory) adapter.getItem(position)).getId()); - final String oldContent = note.getContent(); - String newContent; - if (oldContent != null && oldContent.length() > 0) { - newContent = oldContent + "\n\n" + receivedText; - } else { - newContent = receivedText; - } - mainViewModel.updateNoteAndSync(localAccount, note, newContent, null, () -> Toast.makeText(this, getString(R.string.added_content, receivedText), Toast.LENGTH_SHORT).show()); + final LiveData<NoteWithCategory> fullNote$ = mainViewModel.getFullNoteWithCategory(((NoteWithCategory) adapter.getItem(position)).getId()); + fullNote$.observe(this, (fullNote) -> { + fullNote$.removeObservers(this); + final String oldContent = fullNote.getContent(); + String newContent; + if (oldContent != null && oldContent.length() > 0) { + newContent = oldContent + "\n\n" + receivedText; + } else { + newContent = receivedText; + } + LiveData<Void> updateLiveData = mainViewModel.updateNoteAndSync(fullNote, newContent, null); + updateLiveData.observe(this, (next) -> { + Toast.makeText(this, getString(R.string.added_content, receivedText), Toast.LENGTH_SHORT).show(); + updateLiveData.removeObservers(this); + }); + }); } else { Toast.makeText(this, R.string.shared_text_empty, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherListener.java b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherListener.java index 4075cf7a..87491a16 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherListener.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/accountswitcher/AccountSwitcherListener.java @@ -1,9 +1,11 @@ package it.niedermann.owncloud.notes.accountswitcher; +import androidx.annotation.NonNull; + import it.niedermann.owncloud.notes.persistence.entity.Account; public interface AccountSwitcherListener { void addAccount(); - void onAccountChosen(Account localAccount); + void onAccountChosen(@NonNull Account localAccount); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java index 875baca3..0924aacf 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/BaseNoteFragment.java @@ -108,7 +108,7 @@ public abstract class BaseNoteFragment extends BrandedFragment implements Catego SingleAccountHelper.setCurrentAccount(requireActivity().getApplicationContext(), localAccount.getAccountName()); } isNew = false; - note = originalNote = db.getNoteDao().getNoteWithCategory(localAccount.getId(), id); + note = originalNote = db.getNoteDao().getFullNoteWithCategory(localAccount.getId(), id); } else { NoteWithCategory cloudNote = (NoteWithCategory) requireArguments().getSerializable(PARAM_NEWNOTE); String content = requireArguments().getString(PARAM_CONTENT); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java index f106b86c..5c6fbbef 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/edit/NotePreviewFragment.java @@ -28,7 +28,6 @@ import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.nextcloud.android.sso.model.SingleSignOnAccount; import com.yydcdut.markdown.MarkdownProcessor; import com.yydcdut.markdown.syntax.text.TextFactory; @@ -214,7 +213,7 @@ public class NotePreviewFragment extends SearchableBaseNoteFragment implements O TextProcessorChain chain = defaultTextProcessorChain(note.getNote()); Account account = db.getAccountDao().getLocalAccountByAccountName(SingleAccountHelper.getCurrentSingleSignOnAccount(requireContext()).name); db.getNoteServerSyncHelper().addCallbackPull(account, () -> { - note = db.getNoteDao().getNoteWithCategory(note.getAccountId(), note.getId()); + note = db.getNoteDao().getFullNoteWithCategory(note.getAccountId(), note.getId()); changedText = note.getContent(); binding.singleNoteContent.setText(parseCompat(markdownProcessor, chain.apply(note.getContent()))); binding.swiperefreshlayout.setRefreshing(false); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java index 8a5c9fc8..ecd66689 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainActivity.java @@ -42,9 +42,8 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundExce import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; import com.nextcloud.android.sso.exceptions.TokenMismatchException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.nextcloud.android.sso.model.SingleSignOnAccount; -import java.util.List; +import java.util.stream.Collectors; import it.niedermann.owncloud.notes.ImportAccountActivity; import it.niedermann.owncloud.notes.LockedActivity; @@ -112,8 +111,6 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A private NavigationAdapter adapterCategories; private MenuAdapter menuAdapter; - protected Account localAccount; - protected DrawerLayoutBinding binding; protected ActivityNotesListViewBinding activityBinding; protected FloatingActionButton fabCreate; @@ -228,34 +225,36 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A } else { newMethod = CategorySortingMethod.SORT_LEXICOGRAPHICAL_ASC; } - mainViewModel.modifyCategoryOrder(localAccount.getId(), methodOfCategory.first, newMethod); + final LiveData<Void> modifyLiveData = mainViewModel.modifyCategoryOrder(methodOfCategory.first, newMethod); + modifyLiveData.observe(this, (next) -> modifyLiveData.removeObservers(this)); } }); }); mainViewModel.getNavigationCategories().observe(this, navigationItems -> this.adapterCategories.setItems(navigationItems)); - mainViewModel.getCurrentAccount().observe(this, (a) -> { + mainViewModel.getCurrentAccount().observe(this, (nextAccount) -> { fabCreate.hide(); - localAccount = a; Glide .with(this) - .load(a.getUrl() + "/index.php/avatar/" + Uri.encode(a.getUserName()) + "/64") + .load(nextAccount.getUrl() + "/index.php/avatar/" + Uri.encode(nextAccount.getUserName()) + "/64") .placeholder(R.drawable.ic_account_circle_grey_24dp) .error(R.drawable.ic_account_circle_grey_24dp) .apply(RequestOptions.circleCropTransform()) .into(activityBinding.launchAccountSwitcher); - new NotesListViewItemTouchHelper(a, this, mainViewModel, this, adapter, swipeRefreshLayout, coordinatorLayout, gridView) + new NotesListViewItemTouchHelper(nextAccount, this, mainViewModel, this, adapter, swipeRefreshLayout, coordinatorLayout, gridView) .attachToRecyclerView(listView); - if (!mainViewModel.synchronize(a)) { - BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show(); - } - fabCreate.show(); - activityBinding.launchAccountSwitcher.setOnClickListener((v) -> { - AccountSwitcherDialog.newInstance(localAccount.getId()).show(getSupportFragmentManager(), AccountSwitcherDialog.class.getSimpleName()); + final LiveData<Boolean> syncLiveData = mainViewModel.synchronize(); + syncLiveData.observe(this, (syncSuccess) -> { + syncLiveData.removeObservers(this); + if (!syncSuccess) { + BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show(); + } }); + fabCreate.show(); + activityBinding.launchAccountSwitcher.setOnClickListener((v) -> AccountSwitcherDialog.newInstance(nextAccount.getId()).show(getSupportFragmentManager(), AccountSwitcherDialog.class.getSimpleName())); if (menuAdapter == null) { - menuAdapter = new MenuAdapter(getApplicationContext(), localAccount, (menuItem) -> { + menuAdapter = new MenuAdapter(getApplicationContext(), nextAccount, (menuItem) -> { @Nullable Integer resultCode = menuItem.getResultCode(); if (resultCode == null) { startActivity(menuItem.getIntent()); @@ -266,17 +265,20 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A binding.navigationMenu.setAdapter(menuAdapter); } else { - menuAdapter.updateAccount(a); + menuAdapter.updateAccount(nextAccount); } }); } @Override protected void onResume() { - // refresh and sync every time the activity gets - if (!mainViewModel.synchronize(localAccount)) { - BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show(); - } + final LiveData<Boolean> syncLiveData = mainViewModel.synchronize(); + syncLiveData.observe(this, (syncSuccess) -> { + syncLiveData.removeObservers(this); + if (!syncSuccess) { + BrandedSnackbar.make(coordinatorLayout, getString(R.string.error_sync, getString(R.string.error_no_network)), Snackbar.LENGTH_LONG).show(); + } + }); super.onResume(); } @@ -600,8 +602,8 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A @Override public void onNoteFavoriteClick(int position, View view) { - mainViewModel.toggleFavoriteAndSync(localAccount, ((NoteWithCategory) adapter.getItem(position)).getId()); - adapter.notifyItemChanged(position); + LiveData<Void> toggleLiveData = mainViewModel.toggleFavoriteAndSync(((NoteWithCategory) adapter.getItem(position)).getId()); + toggleLiveData.observe(this, (next) -> toggleLiveData.removeObservers(this)); } @Override @@ -609,7 +611,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A boolean selected = adapter.select(position); if (selected) { v.setSelected(true); - mActionMode = startSupportActionMode(new MultiSelectedActionModeCallback(this, coordinatorLayout, mainViewModel, this, localAccount, canMoveNoteToAnotherAccounts, adapter, listView, getSupportFragmentManager(), activityBinding.searchView)); + mActionMode = startSupportActionMode(new MultiSelectedActionModeCallback(this, coordinatorLayout, mainViewModel, this, canMoveNoteToAnotherAccounts, adapter, listView, getSupportFragmentManager(), activityBinding.searchView)); final int checkedItemCount = adapter.getSelected().size(); mActionMode.setTitle(getResources().getQuantityString(R.plurals.ab_selected, checkedItemCount, checkedItemCount)); } @@ -646,7 +648,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A } @Override - public void onAccountChosen(Account localAccount) { + public void onAccountChosen(@NonNull Account localAccount) { binding.drawerLayout.closeDrawer(GravityCompat.START); mainViewModel.postCurrentAccount(localAccount); } @@ -661,8 +663,12 @@ public class MainActivity extends LockedActivity implements NoteClickListener, A @Override public void onCategoryChosen(String category) { - for (Integer i : adapter.getSelected()) { - mainViewModel.setCategory(localAccount, ((NoteWithCategory) adapter.getItem(i)).getId(), category); - } + final LiveData<Void> categoryLiveData = mainViewModel.setCategory( + adapter.getSelected() + .stream() + .map(item -> ((NoteWithCategory) adapter.getItem(item)).getId()) + .collect(Collectors.toList()) + , category); + categoryLiveData.observe(this, (next) -> categoryLiveData.removeObservers(this)); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java index 8611e59c..131963e4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MainViewModel.java @@ -2,13 +2,14 @@ package it.niedermann.owncloud.notes.main; import android.app.Application; import android.content.Context; +import android.text.TextUtils; import android.util.Log; -import androidx.annotation.AnyThread; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import androidx.arch.core.util.Function; import androidx.core.util.Pair; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; @@ -18,11 +19,13 @@ import com.nextcloud.android.sso.AccountImporter; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException; import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.nextcloud.android.sso.model.SingleSignOnAccount; import java.net.HttpURLConnection; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.BrandingUtil; @@ -38,7 +41,6 @@ import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData; import it.niedermann.owncloud.notes.shared.model.Capabilities; import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod; -import it.niedermann.owncloud.notes.shared.model.ISyncCallback; import it.niedermann.owncloud.notes.shared.model.Item; import it.niedermann.owncloud.notes.shared.model.NavigationCategory; @@ -314,29 +316,44 @@ public class MainViewModel extends AndroidViewModel { return items; } - public void modifyCategoryOrder(long accountId, @NonNull NavigationCategory selectedCategory, @NonNull CategorySortingMethod sortingMethod) { - db.modifyCategoryOrder(accountId, selectedCategory, sortingMethod); + public LiveData<Void> modifyCategoryOrder(@NonNull NavigationCategory selectedCategory, @NonNull CategorySortingMethod sortingMethod) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(null); + } else { + Log.v(TAG, "[modifyCategoryOrder] - currentAccount: " + currentAccount.getAccountName()); + db.modifyCategoryOrder(currentAccount.getId(), selectedCategory, sortingMethod); + return new MutableLiveData<>(null); + } + }); } /** - * @return <code>true</code>, if a synchronization could successfully be triggered. + * @return <code>true</code>, if a synchronization could successfully be triggered, <code>false</code> if not. */ - public boolean synchronize(Account account) { - NoteServerSyncHelper syncHelper = db.getNoteServerSyncHelper(); - if (!syncHelper.isSyncPossible()) { - syncHelper.updateNetworkStatus(); - } - if (syncHelper.isSyncPossible()) { - syncHelper.scheduleSync(account, false); - return true; - } else { // Sync is not possible - if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) { - Log.d(TAG, "Network is connected, but sync is not possible"); + public LiveData<Boolean> synchronize() { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(false); } else { - Log.d(TAG, "Sync is not possible, because network is not connected"); + Log.v(TAG, "[synchronize] - currentAccount: " + currentAccount.getAccountName()); + NoteServerSyncHelper syncHelper = db.getNoteServerSyncHelper(); + if (!syncHelper.isSyncPossible()) { + syncHelper.updateNetworkStatus(); + } + if (syncHelper.isSyncPossible()) { + syncHelper.scheduleSync(currentAccount, false); + return new MutableLiveData<>(true); + } else { // Sync is not possible + if (syncHelper.isNetworkConnected() && syncHelper.isSyncOnlyOnWifi()) { + Log.d(TAG, "Network is connected, but sync is not possible"); + } else { + Log.d(TAG, "Sync is not possible, because network is not connected"); + } + } + return new MutableLiveData<>(false); } - } - return false; + }); } public LiveData<Boolean> getSyncStatus() { @@ -359,36 +376,43 @@ public class MainViewModel extends AndroidViewModel { return insufficientInformation; } else { Log.i(TAG, "[performFullSynchronizationForCurrentAccount] Refreshing capabilities for " + localAccount.getAccountName()); - MutableLiveData<Boolean> syncSuccess = new MutableLiveData<>(); + MutableLiveData<Boolean> syncCapabilitiesLiveData = new MutableLiveData<>(); new Thread(() -> { + final Capabilities capabilities; try { - SingleSignOnAccount ssoAccount = AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()); - final Capabilities capabilities; - try { - capabilities = CapabilitiesClient.getCapabilities(getApplication(), ssoAccount, localAccount.getCapabilitiesETag()); - db.getAccountDao().updateCapabilitiesETag(localAccount.getId(), capabilities.getETag()); - db.getAccountDao().updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor()); - localAccount.setColor(capabilities.getColor()); - localAccount.setTextColor(capabilities.getTextColor()); - BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor()); - db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion()); - Log.i(TAG, capabilities.toString()); - } catch (Exception e) { - if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { - Log.i(TAG, "Capabilities not modified."); - } else { - e.printStackTrace(); - } - } - // Even if the capabilities endpoint makes trouble, we can still try to synchronize the notes - syncSuccess.postValue(synchronize(localAccount)); + capabilities = CapabilitiesClient.getCapabilities(getApplication(), AccountImporter.getSingleSignOnAccount(getApplication(), localAccount.getAccountName()), localAccount.getCapabilitiesETag()); + db.getAccountDao().updateCapabilitiesETag(localAccount.getId(), capabilities.getETag()); + db.getAccountDao().updateBrand(localAccount.getId(), capabilities.getColor(), capabilities.getTextColor()); + localAccount.setColor(capabilities.getColor()); + localAccount.setTextColor(capabilities.getTextColor()); + BrandingUtil.saveBrandColors(getApplication(), localAccount.getColor(), localAccount.getTextColor()); + db.updateApiVersion(localAccount.getId(), capabilities.getApiVersion()); + Log.i(TAG, capabilities.toString()); + syncCapabilitiesLiveData.postValue(true); } catch (NextcloudFilesAppAccountNotFoundException e) { e.printStackTrace(); - // TODO should we just remove this account from the database? - syncSuccess.postValue(true); + db.getAccountDao().deleteAccount(localAccount); + syncCapabilitiesLiveData.postValue(false); + } catch (Exception e) { + if (e instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) e).getStatusCode() == HttpURLConnection.HTTP_NOT_MODIFIED) { + Log.i(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities not modified."); + } else { + e.printStackTrace(); + } + // Capabilities couldn't be update correctly, we can still try to sync the notes list. + syncCapabilitiesLiveData.postValue(true); } + }).start(); - return syncSuccess; + return switchMap(syncCapabilitiesLiveData, (Function<Boolean, LiveData<Boolean>>) capabilitiesSyncedSuccessfully -> { + if (Boolean.TRUE.equals(capabilitiesSyncedSuccessfully)) { + Log.v(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities refreshed successfully - synchronize notes for " + localAccount.getAccountName()); + return synchronize(); + } else { + Log.w(TAG, "[performFullSynchronizationForCurrentAccount] Capabilities could not be refreshed correctly - end synchronization process here."); + return new MutableLiveData<>(true); + } + }); } }); } @@ -403,8 +427,18 @@ public class MainViewModel extends AndroidViewModel { return db.getAccountDao().getAccounts(); } - public void setCategory(Account account, Long noteId, @NonNull String category) { - db.setCategory(account, noteId, category); + public LiveData<Void> setCategory(Collection<Long> noteIds, @NonNull String category) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(null); + } else { + Log.v(TAG, "[setCategory] - currentAccount: " + currentAccount.getAccountName()); + for (Long noteId : noteIds) { + db.setCategory(currentAccount, noteId, category); + } + return new MutableLiveData<>(null); + } + }); } public LiveData<NoteWithCategory> moveNoteToAnotherAccount(Account account, NoteWithCategory note) { @@ -420,30 +454,89 @@ public class MainViewModel extends AndroidViewModel { return db.deleteAccount(account); } - public void toggleFavoriteAndSync(Account account, long noteId) { - db.toggleFavoriteAndSync(account, noteId); + public LiveData<Void> toggleFavoriteAndSync(long noteId) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(null); + } else { + Log.v(TAG, "[toggleFavoriteAndSync] - currentAccount: " + currentAccount.getAccountName()); + db.toggleFavoriteAndSync(currentAccount, noteId); + return new MutableLiveData<>(null); + } + }); + } + + public LiveData<Void> deleteNoteAndSync(long id) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(null); + } else { + Log.v(TAG, "[deleteNoteAndSync] - currentAccount: " + currentAccount.getAccountName()); + db.deleteNoteAndSync(currentAccount, id); + return new MutableLiveData<>(null); + } + }); } - public void deleteNoteAndSync(Account account, long id) { - db.deleteNoteAndSync(account, id); + public LiveData<Void> deleteNotesAndSync(Collection<Long> ids) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(null); + } else { + Log.v(TAG, "[deleteNotesAndSync] - currentAccount: " + currentAccount.getAccountName()); + for (Long id : ids) { + db.deleteNoteAndSync(currentAccount, id); + } + return new MutableLiveData<>(null); + } + }); } public LiveData<Account> addAccount(@NonNull String url, @NonNull String username, @NonNull String accountName, @NonNull Capabilities capabilities) { return db.addAccount(url, username, accountName, capabilities); } - @WorkerThread - public NoteWithCategory getNoteWithCategory(long accountId, long id) { - return db.getNoteDao().getNoteWithCategory(accountId, id); + public LiveData<NoteWithCategory> getFullNoteWithCategory(long id) { + return map(getFullNotesWithCategory(Collections.singleton(id)), input -> input.get(0)); } - public LiveData<NoteWithCategory> addNoteAndSync(Account account, long accountId, NoteWithCategory note) { - return db.addNoteAndSync(account, note); + public LiveData<List<NoteWithCategory>> getFullNotesWithCategory(Collection<Long> ids) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(); + } else { + Log.v(TAG, "[getNoteWithCategory] - currentAccount: " + currentAccount.getAccountName()); + final MutableLiveData<List<NoteWithCategory>> notes = new MutableLiveData<>(); + new Thread(() -> notes.postValue( + ids + .stream() + .map(id -> db.getNoteDao().getFullNoteWithCategory(currentAccount.getId(), id)) + .collect(Collectors.toList()) + )).start(); + return notes; + } + }); } - @WorkerThread - public void updateNoteAndSync(@NonNull Account localAccount, @NonNull NoteWithCategory oldNote, @Nullable String newContent, @Nullable String newTitle, @Nullable ISyncCallback callback) { - db.updateNoteAndSync(localAccount, oldNote, newContent, newTitle, callback); + public LiveData<NoteWithCategory> addNoteAndSync(NoteWithCategory note) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount == null) { + return new MutableLiveData<>(); + } else { + Log.v(TAG, "[addNoteAndSync] - currentAccount: " + currentAccount.getAccountName()); + return db.addNoteAndSync(currentAccount, note); + } + }); + } + + public LiveData<Void> updateNoteAndSync(@NonNull NoteWithCategory oldNote, @Nullable String newContent, @Nullable String newTitle) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount != null) { + Log.v(TAG, "[updateNoteAndSync] - currentAccount: " + currentAccount.getAccountName()); + db.updateNoteAndSync(currentAccount, oldNote, newContent, newTitle, null); + } + return new MutableLiveData<>(null); + }); } public void createOrUpdateSingleNoteWidgetData(SingleNoteWidgetData data) { @@ -453,4 +546,28 @@ public class MainViewModel extends AndroidViewModel { public LiveData<Integer> getAccountsCount() { return db.getAccountDao().getAccountsCountLiveData(); } + + public LiveData<String> collectNoteContents(List<Long> noteIds) { + return switchMap(getCurrentAccount(), currentAccount -> { + if (currentAccount != null) { + Log.v(TAG, "[collectNoteContents] - currentAccount: " + currentAccount.getAccountName()); + final MutableLiveData<String> collectedContent$ = new MutableLiveData<>(); + new Thread(() -> { + final StringBuilder noteContents = new StringBuilder(); + for (Long noteId : noteIds) { + final NoteWithCategory fullNote = db.getNoteDao().getFullNoteWithCategory(currentAccount.getId(), noteId); + final String tempFullNote = fullNote.getContent(); + if (!TextUtils.isEmpty(tempFullNote)) { + if (noteContents.length() > 0) { + noteContents.append("\n\n"); + } + noteContents.append(tempFullNote); + } + } + }).start(); + return collectedContent$; + } + return new MutableLiveData<>(null); + }); + } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java b/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java index 1e4ce1df..b1afad4c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/main/MultiSelectedActionModeCallback.java @@ -2,7 +2,6 @@ package it.niedermann.owncloud.notes.main; import android.content.Context; import android.graphics.drawable.Drawable; -import android.text.TextUtils; import android.util.TypedValue; import android.view.Menu; import android.view.MenuItem; @@ -20,20 +19,16 @@ import androidx.lifecycle.LiveData; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; -import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; -import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException; -import com.nextcloud.android.sso.helper.SingleAccountHelper; -import com.nextcloud.android.sso.model.SingleSignOnAccount; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.accountpicker.AccountPickerDialogFragment; import it.niedermann.owncloud.notes.branding.BrandedSnackbar; import it.niedermann.owncloud.notes.edit.category.CategoryDialogFragment; import it.niedermann.owncloud.notes.main.items.ItemAdapter; -import it.niedermann.owncloud.notes.persistence.NotesDatabase; import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; import it.niedermann.owncloud.notes.shared.util.ShareUtil; @@ -50,7 +45,6 @@ public class MultiSelectedActionModeCallback implements Callback { private final MainViewModel mainViewModel; @NonNull private final LifecycleOwner lifecycleOwner; - private final Account account; private final boolean canMoveNoteToAnotherAccounts; private final ItemAdapter adapter; private final RecyclerView recyclerView; @@ -58,12 +52,11 @@ public class MultiSelectedActionModeCallback implements Callback { private final SearchView searchView; public MultiSelectedActionModeCallback( - @NonNull Context context, @NonNull View view, @NonNull MainViewModel mainViewModel, @NonNull LifecycleOwner lifecycleOwner, @NonNull Account account, boolean canMoveNoteToAnotherAccounts, ItemAdapter adapter, RecyclerView recyclerView, FragmentManager fragmentManager, SearchView searchView) { + @NonNull Context context, @NonNull View view, @NonNull MainViewModel mainViewModel, @NonNull LifecycleOwner lifecycleOwner, boolean canMoveNoteToAnotherAccounts, ItemAdapter adapter, RecyclerView recyclerView, FragmentManager fragmentManager, SearchView searchView) { this.context = context; this.view = view; this.mainViewModel = mainViewModel; this.lifecycleOwner = lifecycleOwner; - this.account = account; this.canMoveNoteToAnotherAccounts = canMoveNoteToAnotherAccounts; this.adapter = adapter; this.recyclerView = recyclerView; @@ -105,59 +98,63 @@ public class MultiSelectedActionModeCallback implements Callback { public boolean onActionItemClicked(ActionMode mode, MenuItem item) { int itemId = item.getItemId(); if (itemId == R.id.menu_delete) { - List<NoteWithCategory> deletedNotes = new ArrayList<>(); - List<Integer> selection = adapter.getSelected(); - for (Integer i : selection) { - NoteWithCategory note = (NoteWithCategory) adapter.getItem(i); - deletedNotes.add(mainViewModel.getNoteWithCategory(note.getAccountId(), note.getId())); - mainViewModel.deleteNoteAndSync(account, note.getId()); - } - mode.finish(); // Action picked, so close the CAB - //after delete selection has to be cleared + final List<Long> selection = adapter.getSelected().stream().map(itemPosition -> ((NoteWithCategory) adapter.getItem(itemPosition)).getId()).collect(Collectors.toList()); + final LiveData<List<NoteWithCategory>> fullNotes$ = mainViewModel.getFullNotesWithCategory(selection); + fullNotes$.observe(lifecycleOwner, (fullNotes) -> { + fullNotes$.removeObservers(lifecycleOwner); searchView.setIconified(true); - String deletedSnackbarTitle = deletedNotes.size() == 1 - ? context.getString(R.string.action_note_deleted, deletedNotes.get(0).getTitle()) - : context.getResources().getQuantityString(R.plurals.bulk_notes_deleted, deletedNotes.size(), deletedNotes.size()); + final LiveData<Void> deleteLiveData = mainViewModel.deleteNotesAndSync(fullNotes.stream().map(NoteWithCategory::getId).collect(Collectors.toList())); + deleteLiveData.observe(lifecycleOwner, (next) -> deleteLiveData.removeObservers(lifecycleOwner)); + String deletedSnackbarTitle = fullNotes.size() == 1 + ? context.getString(R.string.action_note_deleted, fullNotes.get(0).getTitle()) + : context.getResources().getQuantityString(R.plurals.bulk_notes_deleted, fullNotes.size(), fullNotes.size()); BrandedSnackbar.make(view, deletedSnackbarTitle, Snackbar.LENGTH_LONG) .setAction(R.string.action_undo, (View v) -> { - for (NoteWithCategory deletedNote : deletedNotes) { - final LiveData<NoteWithCategory> undoLiveData = mainViewModel.addNoteAndSync(account, deletedNote.getAccountId(), deletedNote); + for (NoteWithCategory deletedNote : fullNotes) { + final LiveData<NoteWithCategory> undoLiveData = mainViewModel.addNoteAndSync(deletedNote); undoLiveData.observe(lifecycleOwner, (o) -> undoLiveData.removeObservers(lifecycleOwner)); } - String restoreSnackbarTitle = deletedNotes.size() == 1 - ? context.getString(R.string.action_note_restored, deletedNotes.get(0).getTitle()) - : context.getResources().getQuantityString(R.plurals.bulk_notes_restored, deletedNotes.size(), deletedNotes.size()); + String restoreSnackbarTitle = fullNotes.size() == 1 + ? context.getString(R.string.action_note_restored, fullNotes.get(0).getTitle()) + : context.getResources().getQuantityString(R.plurals.bulk_notes_restored, fullNotes.size(), fullNotes.size()); BrandedSnackbar.make(view, restoreSnackbarTitle, Snackbar.LENGTH_SHORT) .show(); }) .show(); + }); return true; } else if (itemId == R.id.menu_move) { - AccountPickerDialogFragment - .newInstance(account.getId()) - .show(fragmentManager, MainActivity.class.getSimpleName()); + final LiveData<Account> accountLiveData = mainViewModel.getCurrentAccount(); + accountLiveData.observe(lifecycleOwner, account -> { + accountLiveData.removeObservers(lifecycleOwner); + AccountPickerDialogFragment + .newInstance(account.getId()) + .show(fragmentManager, AccountPickerDialogFragment.class.getSimpleName()); + }); return true; } else if (itemId == R.id.menu_share) { final String subject = (adapter.getSelected().size() == 1) ? ((NoteWithCategory) adapter.getItem(adapter.getSelected().get(0))).getTitle() : context.getResources().getQuantityString(R.plurals.share_multiple, adapter.getSelected().size(), adapter.getSelected().size()); - final StringBuilder noteContents = new StringBuilder(); - for (Integer i : adapter.getSelected()) { - final NoteWithCategory noteWithoutContent = (NoteWithCategory) adapter.getItem(i); - final String tempFullNote = mainViewModel.getNoteWithCategory(noteWithoutContent.getAccountId(), noteWithoutContent.getId()).getContent(); - if (!TextUtils.isEmpty(tempFullNote)) { - if (noteContents.length() > 0) { - noteContents.append("\n\n"); - } - noteContents.append(tempFullNote); - } - } - ShareUtil.openShareDialog(context, subject, noteContents.toString()); + + final LiveData<String> contentCollector = mainViewModel.collectNoteContents( + adapter.getSelected() + .stream() + .map(itemPosition -> ((NoteWithCategory) adapter.getItem(itemPosition)).getId()) + .collect(Collectors.toList())); + contentCollector.observe(lifecycleOwner, (next) -> { + contentCollector.removeObservers(lifecycleOwner); + ShareUtil.openShareDialog(context, subject, next); + }); return true; } else if (itemId == R.id.menu_category) {// TODO detect whether all selected notes do have the same category - in this case preselect it - CategoryDialogFragment - .newInstance(account.getId(), "") - .show(fragmentManager, CategoryDialogFragment.class.getSimpleName()); + final LiveData<Account> accountLiveData = mainViewModel.getCurrentAccount(); + accountLiveData.observe(lifecycleOwner, account -> { + accountLiveData.removeObservers(lifecycleOwner); + CategoryDialogFragment + .newInstance(account.getId(), "") + .show(fragmentManager, CategoryDialogFragment.class.getSimpleName()); + }); return false; } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/main/items/list/NotesListViewItemTouchHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/main/items/list/NotesListViewItemTouchHelper.java index cac3dab6..386f6dbe 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/main/items/list/NotesListViewItemTouchHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/main/items/list/NotesListViewItemTouchHelper.java @@ -73,25 +73,30 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper { switch (direction) { case ItemTouchHelper.LEFT: final NoteWithCategory dbNoteWithoutContent = (NoteWithCategory) adapter.getItem(viewHolder.getAdapterPosition()); - final NoteWithCategory dbNote = mainViewModel.getNoteWithCategory(dbNoteWithoutContent.getAccountId(), dbNoteWithoutContent.getId()); - mainViewModel.deleteNoteAndSync(account, dbNote.getId()); - Log.v(TAG, "Item deleted through swipe ----------------------------------------------"); - if (view == null) { - Toast.makeText(context, context.getString(R.string.action_note_deleted, dbNote.getTitle()), Toast.LENGTH_LONG).show(); - } else { - BrandedSnackbar.make(view, context.getString(R.string.action_note_deleted, dbNote.getTitle()), UNDO_DURATION) - .setAction(R.string.action_undo, (View v) -> { - final LiveData<NoteWithCategory> undoLiveData = mainViewModel.addNoteAndSync(account, dbNote.getAccountId(), dbNote); - undoLiveData.observe(lifecycleOwner, (o) -> undoLiveData.removeObservers(lifecycleOwner)); - BrandedSnackbar.make(view, context.getString(R.string.action_note_restored, dbNote.getTitle()), Snackbar.LENGTH_SHORT) - .show(); - }) - .show(); - } + final LiveData<NoteWithCategory> dbNoteLiveData = mainViewModel.getFullNoteWithCategory(dbNoteWithoutContent.getId()); + dbNoteLiveData.observe(lifecycleOwner, (dbNote) -> { + dbNoteLiveData.removeObservers(lifecycleOwner); + final LiveData<Void> deleteLiveData = mainViewModel.deleteNoteAndSync(dbNote.getId()); + deleteLiveData.observe(lifecycleOwner, (next) -> deleteLiveData.removeObservers(lifecycleOwner)); + Log.v(TAG, "Item deleted through swipe ----------------------------------------------"); + if (view == null) { + Toast.makeText(context, context.getString(R.string.action_note_deleted, dbNote.getTitle()), Toast.LENGTH_LONG).show(); + } else { + BrandedSnackbar.make(view, context.getString(R.string.action_note_deleted, dbNote.getTitle()), UNDO_DURATION) + .setAction(R.string.action_undo, (View v) -> { + final LiveData<NoteWithCategory> undoLiveData = mainViewModel.addNoteAndSync(dbNote); + undoLiveData.observe(lifecycleOwner, (o) -> undoLiveData.removeObservers(lifecycleOwner)); + BrandedSnackbar.make(view, context.getString(R.string.action_note_restored, dbNote.getTitle()), Snackbar.LENGTH_SHORT) + .show(); + }) + .show(); + } + }); break; case ItemTouchHelper.RIGHT: final NoteWithCategory adapterNote = (NoteWithCategory) adapter.getItem(viewHolder.getAdapterPosition()); - mainViewModel.toggleFavoriteAndSync(account, adapterNote.getId()); + LiveData<Void> toggleLiveData = mainViewModel.toggleFavoriteAndSync(adapterNote.getId()); + toggleLiveData.observe(lifecycleOwner, (next) -> toggleLiveData.removeObservers(lifecycleOwner)); break; default: //NoOp diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java index 6ed08abf..3fa1c652 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NotesDatabase.java @@ -25,7 +25,6 @@ import androidx.sqlite.db.SupportSQLiteDatabase; import com.nextcloud.android.sso.AccountImporter; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; -import com.nextcloud.android.sso.model.SingleSignOnAccount; import org.json.JSONArray; import org.json.JSONException; @@ -203,7 +202,7 @@ public abstract class NotesDatabase extends RoomDatabase { entity.setFavorite(note.getFavorite()); entity.setCategoryId(getOrCreateCategoryIdByTitle(accountId, note.getCategory())); entity.setETag(note.getETag()); - return getNoteDao().getNoteWithCategory(accountId, getNoteDao().addNote(entity)); + return getNoteDao().getFullNoteWithCategory(accountId, getNoteDao().addNote(entity)); } @AnyThread @@ -238,7 +237,6 @@ public abstract class NotesDatabase extends RoomDatabase { * If there is no such category existing, this method will create it and search again. * * @param account The single sign on account - * @param accountId The account where the note is * @param noteId The note which will be updated * @param category The category title which should be used to find the category id. */ diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java index 9b1db4a0..58c3d567 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/NoteDao.java @@ -74,7 +74,10 @@ public interface NoteDao { void updateScrollY(long id, int scrollY); @Query("SELECT NOTE.*, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.id = :id AND NOTE.accountId = :accountId AND status != :accountId") - NoteWithCategory getNoteWithCategory(long accountId, long id); + NoteWithCategory getFullNoteWithCategory(long accountId, long id); + + @Query("SELECT NOTE.*, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.id = :id AND NOTE.accountId = :accountId AND status != :accountId") + LiveData<NoteWithCategory> getNoteWithCategoryLiveData(long accountId, long id); @Query("UPDATE NOTE SET status = :status WHERE id = :id") void updateStatus(long id, DBStatus status); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java index 6e7f8537..5206409e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/singlenote/SingleNoteWidgetFactory.java @@ -15,7 +15,6 @@ import it.niedermann.owncloud.notes.NotesApplication; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.edit.EditNoteActivity; import it.niedermann.owncloud.notes.persistence.NotesDatabase; -import it.niedermann.owncloud.notes.persistence.entity.Note; import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; import it.niedermann.owncloud.notes.persistence.entity.SingleNoteWidgetData; import it.niedermann.owncloud.notes.preferences.DarkModeSetting; @@ -62,7 +61,7 @@ public class SingleNoteWidgetFactory implements RemoteViewsService.RemoteViewsFa if (data != null) { final long noteId = data.getNoteId(); Log.v(TAG, "Fetch note with id " + noteId); - note = db.getNoteDao().getNoteWithCategory(data.getAccountId(), noteId); + note = db.getNoteDao().getFullNoteWithCategory(data.getAccountId(), noteId); if (note == null) { Log.e(TAG, "Error: note not found"); |