Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2023-03-01 14:43:53 +0300
committerStefan Niedermann <info@niedermann.it>2023-03-09 11:53:19 +0300
commit3ea462ca9e2ae18ba9d869125da8d8d07f2c7854 (patch)
tree30257c67768325d5972ec499a6eb41e11017ac6d /app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters
parentbfab286b0bc6dbfac1211eec64d74b66b2ce1e6d (diff)
refactor: Unidirectional data flow and single point of truth for current state
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java54
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java444
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java18
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_31_32.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java75
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java32
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java75
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java93
11 files changed, 410 insertions, 422 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
index 4406ce78e..e5cdbf192 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
@@ -4,16 +4,14 @@ import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
import android.content.Context;
import android.content.SharedPreferences;
-import android.net.ConnectivityManager;
-import android.net.NetworkInfo;
import android.net.Uri;
import android.webkit.MimeTypeMap;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
import com.nextcloud.android.sso.api.ParsedResponse;
+import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.io.File;
import java.util.List;
@@ -44,6 +42,7 @@ import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
import it.niedermann.nextcloud.deck.model.propagation.CardUpdate;
import it.niedermann.nextcloud.deck.model.propagation.Reorder;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.util.ConnectivityUtil;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
@@ -51,43 +50,36 @@ import okhttp3.ResponseBody;
public class ServerAdapter {
- private final String prefKeyWifiOnly;
+ private final ConnectivityUtil connectivityUtil;
private final String prefKeyEtags;
- final SharedPreferences sharedPreferences;
-
- @NonNull
- private final Context applicationContext;
+ private final SharedPreferences sharedPreferences;
private final ApiProvider provider;
- public ServerAdapter(@NonNull Context applicationContext, @Nullable String ssoAccountName) {
- this.applicationContext = applicationContext;
- prefKeyWifiOnly = applicationContext.getResources().getString(R.string.pref_key_wifi_only);
- prefKeyEtags = applicationContext.getResources().getString(R.string.pref_key_etags);
- provider = new ApiProvider(applicationContext, ssoAccountName);
- sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
+ public ServerAdapter(@NonNull Context context,
+ @NonNull SingleSignOnAccount ssoAccount,
+ @NonNull ConnectivityUtil connectivityUtil) {
+ this(context, new ApiProvider(context, ssoAccount), connectivityUtil);
}
- public void ensureInternetConnection() {
- final boolean isConnected = hasInternetConnection();
- if (!isConnected) {
- throw new OfflineException();
- }
+ public ServerAdapter(@NonNull Context context,
+ @NonNull ApiProvider apiProvider,
+ @NonNull ConnectivityUtil connectivityUtil) {
+ this.prefKeyEtags = context.getResources().getString(R.string.pref_key_etags);
+ this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ this.connectivityUtil = connectivityUtil;
+ this.provider = apiProvider;
}
+ @Deprecated()
public boolean hasInternetConnection() {
- ConnectivityManager cm = (ConnectivityManager) applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm != null) {
- if (sharedPreferences.getBoolean(prefKeyWifiOnly, false)) {
- NetworkInfo networkInfo = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
- if (networkInfo == null) {
- return false;
- }
- return networkInfo.isConnected();
- } else {
- return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
- }
+ return connectivityUtil.hasInternetConnection();
+ }
+
+ public void ensureInternetConnection() {
+ final boolean isConnected = connectivityUtil.hasInternetConnection();
+ if (!isConnected) {
+ throw new OfflineException();
}
- return false;
}
// TODO what is this?
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
index 5c438c421..284abf7d1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java
@@ -1,11 +1,16 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
-import static androidx.lifecycle.Transformations.distinctUntilChanged;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.concurrent.CompletableFuture.supplyAsync;
+import static java.util.stream.Collectors.toList;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
+import android.content.SharedPreferences;
import android.database.sqlite.SQLiteConstraintException;
+import android.text.TextUtils;
import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
@@ -13,27 +18,34 @@ import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
+import androidx.core.content.ContextCompat;
import androidx.lifecycle.LiveData;
+import androidx.preference.PreferenceManager;
import androidx.sqlite.db.SimpleSQLiteQuery;
-import org.jetbrains.annotations.NotNull;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
+import it.niedermann.android.reactivelivedata.ReactiveLiveData;
+import it.niedermann.android.sharedpreferences.SharedPreferenceLongLiveData;
import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
@@ -77,27 +89,40 @@ import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
import it.niedermann.nextcloud.deck.model.widget.filter.dto.FilterWidgetCard;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
import it.niedermann.nextcloud.deck.ui.upcomingcards.UpcomingCardsAdapterItem;
import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget;
public class DataBaseAdapter {
-
@NonNull
private final DeckDatabase db;
@NonNull
private final Context context;
@NonNull
private final ExecutorService widgetNotifierExecutor;
+ @NonNull
+ private final ExecutorService executor;
+ private static final Long NOT_AVAILABLE = -1L;
+ private final SharedPreferences sharedPreferences;
+ private final SharedPreferences.Editor sharedPreferencesEditor;
+ @ColorInt
+ private final int defaultColor;
public DataBaseAdapter(@NonNull Context appContext) {
- this(appContext, DeckDatabase.getInstance(appContext), Executors.newCachedThreadPool());
+ this(appContext, DeckDatabase.getInstance(appContext), Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
}
- private DataBaseAdapter(@NonNull Context applicationContext, @NonNull DeckDatabase db, @NonNull ExecutorService widgetNotifierExecutor) {
+ @VisibleForTesting
+ protected DataBaseAdapter(@NonNull Context applicationContext,
+ @NonNull DeckDatabase db,
+ @NonNull ExecutorService widgetNotifierExecutor,
+ @NonNull ExecutorService executor) {
this.context = applicationContext;
this.db = db;
this.widgetNotifierExecutor = widgetNotifierExecutor;
+ this.executor = executor;
+ this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ this.sharedPreferencesEditor = this.sharedPreferences.edit();
+ this.defaultColor = ContextCompat.getColor(context, R.color.defaultBrand);
}
@NonNull
@@ -118,11 +143,14 @@ public class DataBaseAdapter {
}
public LiveData<Boolean> hasAccounts() {
- return LiveDataHelper.postCustomValue(db.getAccountDao().countAccounts(), data -> data != null && data > 0);
+ return new ReactiveLiveData<>(db.getAccountDao().countAccounts())
+ .distinctUntilChanged()
+ .map(count -> count != null && count > 0);
}
public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
- return distinctUntilChanged(db.getBoardDao().getBoardByRemoteId(accountId, remoteId));
+ return new ReactiveLiveData<>(db.getBoardDao().getBoardByRemoteId(accountId, remoteId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -139,7 +167,8 @@ public class DataBaseAdapter {
}
public LiveData<Stack> getStackByRemoteId(long accountId, long localBoardId, long remoteId) {
- return distinctUntilChanged(db.getStackDao().getStackByRemoteId(accountId, localBoardId, remoteId));
+ return new ReactiveLiveData<>(db.getStackDao().getStackByRemoteId(accountId, localBoardId, remoteId))
+ .distinctUntilChanged();
}
public Stack getStackByLocalIdDirectly(final long localStackId) {
@@ -156,7 +185,8 @@ public class DataBaseAdapter {
}
public LiveData<Card> getCardByRemoteID(long accountId, long remoteId) {
- return distinctUntilChanged(db.getCardDao().getCardByRemoteId(accountId, remoteId));
+ return new ReactiveLiveData<>(db.getCardDao().getCardByRemoteId(accountId, remoteId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -225,16 +255,18 @@ public class DataBaseAdapter {
return db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteId);
}
- public LiveData<List<FullCard>> getFullCardsForStack(long accountId, long localStackId, FilterInformation filter) {
- if (filter == null) {
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardsForStack(accountId, localStackId), this::filterRelationsForCard);
- }
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFilteredFullCardsForStack(getQueryForFilter(filter, accountId, localStackId)), this::filterRelationsForCard);
+ public LiveData<List<FullCard>> getFullCardsForStack(long accountId, long localStackId, @Nullable FilterInformation filter) {
+ return new ReactiveLiveData<>(
+ filter == null
+ ? db.getCardDao().getFullCardsForStack(accountId, localStackId)
+ : db.getCardDao().getFilteredFullCardsForStack(getQueryForFilter(filter, accountId, localStackId)))
+ .tap(this::filterRelationsForCard, executor)
+ .distinctUntilChanged();
}
private void fillSqlWithEntityListValues(StringBuilder query, Collection<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
- List<Long> idList = entities.stream().map(IRemoteEntity::getLocalId).collect(Collectors.toList());
+ List<Long> idList = entities.stream().map(IRemoteEntity::getLocalId).collect(toList());
fillSqlWithListValues(query, args, idList);
}
@@ -257,19 +289,19 @@ public class DataBaseAdapter {
@AnyThread
private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, long accountId, long localStackId) {
- return getQueryForFilter(filter, Collections.singletonList(accountId), Collections.singletonList(localStackId));
+ return getQueryForFilter(filter, singletonList(accountId), singletonList(localStackId));
}
@AnyThread
- private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, List<Long> accountIds, List<Long> localStackIds) {
+ private SimpleSQLiteQuery getQueryForFilter(@NonNull FilterInformation filter, @NonNull List<Long> accountIds, @NonNull List<Long> localStackIds) {
final Collection<Object> args = new ArrayList<>();
StringBuilder query = new StringBuilder("SELECT * FROM card c WHERE 1=1 ");
- if (accountIds != null && !accountIds.isEmpty()) {
+ if (!accountIds.isEmpty()) {
query.append("and accountId in (");
fillSqlWithListValues(query, args, accountIds);
query.append(") ");
}
- if (localStackIds != null && !localStackIds.isEmpty()) {
+ if (!localStackIds.isEmpty()) {
query.append("and stackId in (");
fillSqlWithListValues(query, args, localStackIds);
query.append(") ");
@@ -335,7 +367,7 @@ public class DataBaseAdapter {
throw new IllegalArgumentException("You need to add your new EDueType value\"" + filter.getDueType() + "\" here!");
}
}
- if (filter.getFilterText() != null && !filter.getFilterText().isEmpty()) {
+ if (!TextUtils.isEmpty(filter.getFilterText())) {
query.append(" and (c.description like ? or c.title like ?) ");
String filterText = "%" + filter.getFilterText() + "%";
args.add(filterText);
@@ -385,7 +417,8 @@ public class DataBaseAdapter {
@UiThread
public LiveData<Label> getLabelByRemoteId(long accountId, long remoteId) {
- return distinctUntilChanged(db.getLabelDao().getLabelByRemoteId(accountId, remoteId));
+ return new ReactiveLiveData<>(db.getLabelDao().getLabelByRemoteId(accountId, remoteId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -529,7 +562,7 @@ public class DataBaseAdapter {
final long id = db.getAccountDao().insert(account);
widgetNotifierExecutor.submit(() -> {
- DeckLog.verbose("Adding new created", Account.class.getSimpleName(), " with ", id, " to all instances of ", EWidgetType.UPCOMING_WIDGET.name());
+ DeckLog.verbose("Adding new created", Account.class.getSimpleName(), "with", id, "to all instances of", EWidgetType.UPCOMING_WIDGET.name());
for (FilterWidget widget : getFilterWidgetsByType(EWidgetType.UPCOMING_WIDGET)) {
widget.getAccounts().add(new FilterWidgetAccount(id, false));
updateFilterWidgetDirectly(widget);
@@ -551,29 +584,29 @@ public class DataBaseAdapter {
@UiThread
public LiveData<Account> readAccount(long id) {
- return distinctUntilChanged(fillAccountsUserName(db.getAccountDao().getAccountById(id)));
+ return new ReactiveLiveData<>(db.getAccountDao().getAccountById(id))
+ .tap(account -> account.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(account.getId(), account.getUserName())), executor)
+ .distinctUntilChanged();
}
@UiThread
public LiveData<Account> readAccount(String name) {
- return distinctUntilChanged(fillAccountsUserName(db.getAccountDao().getAccountByName(name)));
+ return new ReactiveLiveData<>(db.getAccountDao().getAccountByName(name))
+ .tap(account -> account.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(account.getId(), account.getUserName())), executor)
+ .distinctUntilChanged();
}
@UiThread
public LiveData<List<Account>> readAccounts() {
- return distinctUntilChanged(fillAccountsListUserName(db.getAccountDao().getAllAccounts()));
+ return new ReactiveLiveData<>(db.getAccountDao().getAllAccounts())
+ .tap(accounts -> accounts.forEach(account -> account.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(account.getId(), account.getUserName()))), executor)
+ .distinctUntilChanged();
}
- private LiveData<Account> fillAccountsUserName(LiveData<Account> source) {
- return LiveDataHelper.interceptLiveData(distinctUntilChanged(source), data -> data.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(data.getId(), data.getUserName())));
- }
-
- private LiveData<List<Account>> fillAccountsListUserName(LiveData<List<Account>> source) {
- return LiveDataHelper.interceptLiveData(distinctUntilChanged(source), data -> {
- for (Account a : data) {
- a.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(a.getId(), a.getUserName()));
- }
- });
+ public LiveData<Integer> getAccountColor(long accountId) {
+ return new ReactiveLiveData<>(db.getAccountDao().getAccountColor(accountId))
+ .distinctUntilChanged()
+ .map(color -> color == null ? defaultColor : color);
}
@WorkerThread
@@ -588,20 +621,14 @@ public class DataBaseAdapter {
return account;
}
-
- public LiveData<List<Board>> getBoards(long accountId) {
- return distinctUntilChanged(db.getBoardDao().getBoardsForAccount(accountId));
- }
-
public LiveData<List<Board>> getBoards(long accountId, boolean archived) {
- return distinctUntilChanged(
- archived
- ? db.getBoardDao().getArchivedBoardsForAccount(accountId)
- : db.getBoardDao().getNonArchivedBoardsForAccount(accountId));
+ return new ReactiveLiveData<>(db.getBoardDao().getNotDeletedBoards(accountId, archived ? 1 : 0))
+ .distinctUntilChanged();
}
public LiveData<List<Board>> getBoardsWithEditPermission(long accountId) {
- return distinctUntilChanged(db.getBoardDao().getBoardsWithEditPermissionsForAccount(accountId));
+ return new ReactiveLiveData<>(db.getBoardDao().getBoardsWithEditPermissionsForAccount(accountId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -612,14 +639,14 @@ public class DataBaseAdapter {
return id;
}
- public void deleteBoard(Board board, boolean setStatus) {
+ public void deleteBoard(@NonNull Board board, boolean setStatus) {
markAsDeletedIfNeeded(board, setStatus);
db.getBoardDao().update(board);
notifyAllWidgets();
notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.BOARD, board.getLocalId());
}
- public void deleteBoardPhysically(Board board) {
+ public void deleteBoardPhysically(@NonNull Board board) {
db.getBoardDao().delete(board);
notifyAllWidgets();
}
@@ -631,7 +658,8 @@ public class DataBaseAdapter {
}
public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
- return distinctUntilChanged(db.getStackDao().getStacksForBoard(accountId, localBoardId));
+ return new ReactiveLiveData<>(db.getStackDao().getStacksForBoard(accountId, localBoardId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -641,7 +669,8 @@ public class DataBaseAdapter {
@MainThread
public LiveData<FullStack> getStack(long accountId, long localStackId) {
- return distinctUntilChanged(db.getStackDao().getFullStack(accountId, localStackId));
+ return new ReactiveLiveData<>(db.getStackDao().getFullStack(accountId, localStackId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -685,12 +714,16 @@ public class DataBaseAdapter {
@AnyThread
public LiveData<FullCard> getCardByLocalId(long accountId, long localCardId) {
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardByLocalId(accountId, localCardId), this::filterRelationsForCard);
+ return new ReactiveLiveData<>(db.getCardDao().getFullCardByLocalId(accountId, localCardId))
+ .tap(this::filterRelationsForCard, executor)
+ .distinctUntilChanged();
}
@AnyThread
public LiveData<FullCardWithProjects> getCardWithProjectsByLocalId(long accountId, long localCardId) {
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardWithProjectsByLocalId(accountId, localCardId), this::filterRelationsForCard);
+ return new ReactiveLiveData<>(db.getCardDao().getFullCardWithProjectsByLocalId(accountId, localCardId))
+ .tap(this::filterRelationsForCard, executor)
+ .distinctUntilChanged();
}
@WorkerThread
@@ -765,7 +798,9 @@ public class DataBaseAdapter {
}
public LiveData<List<AccessControl>> getAccessControlByLocalBoardId(long accountId, Long localBoardId) {
- return LiveDataHelper.interceptLiveData(db.getAccessControlDao().getAccessControlByLocalBoardId(accountId, localBoardId), this::readRelationsForACL);
+ return new ReactiveLiveData<>(db.getAccessControlDao().getAccessControlByLocalBoardId(accountId, localBoardId))
+ .tap(this::readRelationsForACL, executor)
+ .distinctUntilChanged();
}
public List<AccessControl> getAccessControlByLocalBoardIdDirectly(long accountId, Long localBoardId) {
@@ -789,7 +824,8 @@ public class DataBaseAdapter {
}
public LiveData<FullBoard> getFullBoardById(Long accountId, Long localId) {
- return distinctUntilChanged(db.getBoardDao().getFullBoardById(accountId, localId));
+ return new ReactiveLiveData<>(db.getBoardDao().getFullBoardById(accountId, localId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -798,42 +834,50 @@ public class DataBaseAdapter {
}
public LiveData<User> getUserByLocalId(long accountId, long localId) {
- return db.getUserDao().getUserByLocalId(accountId, localId);
+ return new ReactiveLiveData<>(db.getUserDao().getUserByLocalId(accountId, localId))
+ .distinctUntilChanged();
}
public LiveData<User> getUserByUid(long accountId, String uid) {
- return db.getUserDao().getUserByUid(accountId, uid);
+ return new ReactiveLiveData<>(db.getUserDao().getUserByUid(accountId, uid))
+ .distinctUntilChanged();
}
public LiveData<List<User>> getUsersForAccount(final long accountId) {
- return db.getUserDao().getUsersForAccount(accountId);
+ return new ReactiveLiveData<>(db.getUserDao().getUsersForAccount(accountId))
+ .distinctUntilChanged();
}
public LiveData<List<User>> searchUserByUidOrDisplayName(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, final String searchTerm) {
validateSearchTerm(searchTerm);
- return db.getUserDao().searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%");
+ return new ReactiveLiveData<>(db.getUserDao().searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%"))
+ .distinctUntilChanged();
}
- public List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
+ public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
validateSearchTerm(searchTerm);
- return db.getUserDao().searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
+ return db.getUserDao().searchUserByUidOrDisplayNameForACL(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
}
public LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, String searchTerm) {
validateSearchTerm(searchTerm);
- return db.getLabelDao().searchNotYetAssignedLabelsByTitle(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%");
+ return new ReactiveLiveData<>(db.getLabelDao().searchNotYetAssignedLabelsByTitle(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%"))
+ .distinctUntilChanged();
}
public LiveData<List<User>> findProposalsForUsersToAssign(final long accountId, long boardId, long notAssignedToLocalCardId, final int topX) {
- return db.getUserDao().findProposalsForUsersToAssign(accountId, boardId, notAssignedToLocalCardId, topX);
+ return new ReactiveLiveData<>(db.getUserDao().findProposalsForUsersToAssign(accountId, boardId, notAssignedToLocalCardId, topX))
+ .distinctUntilChanged();
}
public LiveData<List<User>> findProposalsForUsersToAssignForACL(final long accountId, long boardId, final int topX) {
- return db.getUserDao().findProposalsForUsersToAssignForACL(accountId, boardId, topX);
+ return new ReactiveLiveData<>(db.getUserDao().findProposalsForUsersToAssignForACL(accountId, boardId, topX))
+ .distinctUntilChanged();
}
public LiveData<List<Label>> findProposalsForLabelsToAssign(final long accountId, final long boardId, long notAssignedToLocalCardId) {
- return db.getLabelDao().findProposalsForLabelsToAssign(accountId, boardId, notAssignedToLocalCardId);
+ return new ReactiveLiveData<>(db.getLabelDao().findProposalsForLabelsToAssign(accountId, boardId, notAssignedToLocalCardId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -923,7 +967,8 @@ public class DataBaseAdapter {
}
public LiveData<Label> getLabelByLocalId(long localLabelId) {
- return db.getLabelDao().getLabelByLocalId(localLabelId);
+ return new ReactiveLiveData<>(db.getLabelDao().getLabelByLocalId(localLabelId))
+ .distinctUntilChanged();
}
public List<FullBoard> getLocallyChangedBoards(long accountId) {
@@ -1002,7 +1047,8 @@ public class DataBaseAdapter {
}
public LiveData<List<Activity>> getActivitiesForCard(Long localCardId) {
- return db.getActivityDao().getActivitiesForCard(localCardId);
+ return new ReactiveLiveData<>(db.getActivityDao().getActivitiesForCard(localCardId))
+ .distinctUntilChanged();
}
public long createActivity(long accountId, Activity activity) {
@@ -1033,22 +1079,22 @@ public class DataBaseAdapter {
}
public LiveData<List<DeckComment>> getCommentsForLocalCardId(long localCardId) {
- return LiveDataHelper.interceptLiveData(db.getCommentDao().getCommentByLocalCardId(localCardId), (list) -> {
- for (DeckComment deckComment : list) {
- deckComment.setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(deckComment.getLocalId()));
- }
- });
+ return new ReactiveLiveData<>(db.getCommentDao().getCommentByLocalCardId(localCardId))
+ .tap(list -> list.forEach(comment -> comment.setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(comment.getLocalId()))), executor)
+ .distinctUntilChanged();
}
public LiveData<List<FullDeckComment>> getFullCommentsForLocalCardId(long localCardId) {
- return LiveDataHelper.interceptLiveData(db.getCommentDao().getFullCommentByLocalCardId(localCardId), (list) -> {
- for (FullDeckComment deckComment : list) {
- deckComment.getComment().setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(deckComment.getLocalId()));
- if (deckComment.getParent() != null) {
- deckComment.getParent().setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(deckComment.getComment().getParentId()));
- }
- }
- });
+ return new ReactiveLiveData<>(db.getCommentDao().getFullCommentByLocalCardId(localCardId))
+ .tap(list -> {
+ for (FullDeckComment deckComment : list) {
+ deckComment.getComment().setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(deckComment.getLocalId()));
+ if (deckComment.getParent() != null) {
+ deckComment.getParent().setMentions(db.getMentionDao().getMentionsForCommentIdDirectly(deckComment.getComment().getParentId()));
+ }
+ }
+ }, executor)
+ .distinctUntilChanged();
}
@WorkerThread
@@ -1114,7 +1160,8 @@ public class DataBaseAdapter {
}
public LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccountId(long cardRemoteId, long accountId) {
- return db.getBoardDao().getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, accountId);
+ return new ReactiveLiveData<>(db.getBoardDao().getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, accountId))
+ .distinctUntilChanged();
}
@WorkerThread
@@ -1138,11 +1185,14 @@ public class DataBaseAdapter {
}
public LiveData<List<FullBoard>> getFullBoards(long accountId, boolean archived) {
- return db.getBoardDao().getArchivedFullBoards(accountId, (archived ? 1 : 0));
+ return new ReactiveLiveData<>(db.getBoardDao().getNotDeletedFullBoards(accountId, archived ? 1 : 0))
+ .distinctUntilChanged();
}
public LiveData<Boolean> hasArchivedBoards(long accountId) {
- return LiveDataHelper.postCustomValue(distinctUntilChanged(db.getBoardDao().countArchivedBoards(accountId)), data -> data != null && data > 0);
+ return new ReactiveLiveData<>(db.getBoardDao().countArchivedBoards(accountId))
+ .distinctUntilChanged()
+ .map(hasArchivedBoards -> hasArchivedBoards != null && hasArchivedBoards > 0);
}
@WorkerThread
@@ -1269,14 +1319,16 @@ public class DataBaseAdapter {
}
public LiveData<List<UpcomingCardsAdapterItem>> getCardsForUpcomingCard() {
- return LiveDataHelper.postCustomValue(db.getCardDao().getUpcomingCards(), this::cardResultsToUpcomingCardsAdapterItems);
+ return new ReactiveLiveData<>(db.getCardDao().getUpcomingCards())
+ .map(this::cardResultsToUpcomingCardsAdapterItems, executor)
+ .distinctUntilChanged();
}
public List<UpcomingCardsAdapterItem> getCardsForUpcomingCardForWidget() {
return cardResultsToUpcomingCardsAdapterItems(db.getCardDao().getUpcomingCardsDirectly());
}
- @NotNull
+ @NonNull
private List<UpcomingCardsAdapterItem> cardResultsToUpcomingCardsAdapterItems(List<FullCard> cardsResult) {
filterRelationsForCard(cardsResult);
final List<UpcomingCardsAdapterItem> result = new ArrayList<>(cardsResult.size());
@@ -1302,7 +1354,7 @@ public class DataBaseAdapter {
} else filter.setDueType(EDueType.NO_FILTER);
if (filterWidget.getAccounts().isEmpty()) {
- cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, null, null)));
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, emptyList(), emptyList())));
} else {
for (FilterWidgetAccount account : filterWidget.getAccounts()) {
filter.setNoAssignedUser(account.isIncludeNoUser());
@@ -1328,24 +1380,21 @@ public class DataBaseAdapter {
if (!account.getBoards().isEmpty()) {
for (FilterWidgetBoard board : account.getBoards()) {
filter.setNoAssignedLabel(board.isIncludeNoLabel());
- final List<Long> stacks;
+ final List<Long> stacks = new ArrayList<>();
for (FilterWidgetLabel label : board.getLabels()) {
Label l = new Label();
l.setLocalId(label.getLabelId());
filter.addLabel(l);
}
if (board.getStacks().isEmpty()) {
- stacks = db.getStackDao().getLocalStackIdsByLocalBoardIdDirectly(board.getBoardId());
+ stacks.addAll(db.getStackDao().getLocalStackIdsByLocalBoardIdDirectly(board.getBoardId()));
} else {
- stacks = new ArrayList<>();
- for (FilterWidgetStack stack : board.getStacks()) {
- stacks.add(stack.getStackId());
- }
+ stacks.addAll(board.getStacks().stream().map(FilterWidgetStack::getStackId).collect(toList()));
}
- cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, Collections.singletonList(account.getAccountId()), stacks)));
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, singletonList(account.getAccountId()), stacks)));
}
} else {
- cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, Collections.singletonList(account.getAccountId()), null)));
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, singletonList(account.getAccountId()), emptyList())));
}
}
}
@@ -1378,21 +1427,17 @@ public class DataBaseAdapter {
private void handleWidgetTypeExtras(FilterWidget filterWidget, Collection<FullCard> cardsResult) {
if (filterWidget.getWidgetType() == EWidgetType.UPCOMING_WIDGET) {
// https://github.com/stefan-niedermann/nextcloud-deck/issues/819 "no due" cards are only shown if they are on a shared board
- for (FullCard fullCard : new ArrayList<>(cardsResult)) {
- if (fullCard.getCard().getDueDate() == null && !db.getStackDao().isStackOnSharedBoardDirectly(fullCard.getCard().getStackId())) {
- cardsResult.remove(fullCard);
- }
- }
+ cardsResult.removeIf(fullCard -> fullCard.getCard().getDueDate() == null && !db.getStackDao().isStackOnSharedBoardDirectly(fullCard.getCard().getStackId()));
List<Long> accountIds = null;
if (!filterWidget.getAccounts().isEmpty()) {
- accountIds = filterWidget.getAccounts().stream().map(FilterWidgetAccount::getAccountId).collect(Collectors.toList());
+ accountIds = filterWidget.getAccounts().stream().map(FilterWidgetAccount::getAccountId).collect(toList());
}
// https://github.com/stefan-niedermann/nextcloud-deck/issues/822 exclude archived cards and boards
final List<Long> archivedStacks = db.getStackDao().getLocalStackIdsInArchivedBoardsByAccountIdsDirectly(accountIds);
for (Long archivedStack : archivedStacks) {
final List<FullCard> archivedCards = cardsResult.stream()
.filter(c -> c.getCard().isArchived() || archivedStack.equals(c.getCard().getStackId()))
- .collect(Collectors.toList());
+ .collect(toList());
cardsResult.removeAll(archivedCards);
}
// https://github.com/stefan-niedermann/nextcloud-deck/issues/800 all cards within non-shared boards need to be included
@@ -1420,7 +1465,8 @@ public class DataBaseAdapter {
}
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
- return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%" + host + "%", boardRemoteId);
+ return new ReactiveLiveData<>(db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%" + host + "%", boardRemoteId))
+ .distinctUntilChanged();
}
public List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String host, long boardRemoteId) {
@@ -1464,14 +1510,16 @@ public class DataBaseAdapter {
}
public LiveData<Integer> countProjectResourcesInProject(Long projectLocalId) {
- return db.getOcsProjectResourceDao().countProjectResourcesInProject(projectLocalId);
+ return new ReactiveLiveData<>(db.getOcsProjectResourceDao().countProjectResourcesInProject(projectLocalId))
+ .distinctUntilChanged();
}
public LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long projectLocalId) {
- return db.getOcsProjectResourceDao().getResourcesByLocalProjectId(projectLocalId);
+ return new ReactiveLiveData<>(db.getOcsProjectResourceDao().getResourcesByLocalProjectId(projectLocalId))
+ .distinctUntilChanged();
}
- public void assignCardToProjectIfMissng(Long accountId, Long localProjectId, Long remoteCardId) {
+ public void assignCardToProjectIfMissing(Long accountId, Long localProjectId, Long remoteCardId) {
final Card card = db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteCardId);
if (card != null) {
final JoinCardWithProject existing = db.getJoinCardWithOcsProjectDao().getAssignmentByCardIdAndProjectIdDirectly(card.getLocalId(), localProjectId);
@@ -1502,6 +1550,12 @@ public class DataBaseAdapter {
// UpcomingWidget.notifyDatasetChanged(context);
}
+ public LiveData<Integer> getBoardColor$(long accountId, long localBoardId) {
+ return new ReactiveLiveData<>(db.getBoardDao().getBoardColor(accountId, localBoardId))
+ .map(color -> color == null ? defaultColor : color)
+ .distinctUntilChanged();
+ }
+
@ColorInt
public Integer getBoardColorDirectly(long accountId, long localBoardId) {
return db.getBoardDao().getBoardColorByLocalIdDirectly(accountId, localBoardId);
@@ -1514,4 +1568,182 @@ public class DataBaseAdapter {
public void deleteProjectResourcesByCardIdDirectly(Long localCardId) {
db.getJoinCardWithOcsProjectDao().deleteProjectResourcesByCardIdDirectly(localCardId);
}
+
+ // =============================================================================================
+ // APP STATE
+ // TODO last boards and stacks per account should be moved to a table to benefit from cascading
+ // =============================================================================================
+
+ // ---------------
+ // Current account
+ // ---------------
+
+ public void saveCurrentAccount(@NonNull Account account) {
+ executor.submit(() -> {
+ // Glide Module depends on correct account being set.
+ // TODO Use SingleSignOnURL where possible, allow passing ssoAccountName to MarkdownEditor
+ SingleAccountHelper.setCurrentAccount(context, account.getName());
+
+ DeckLog.log("--- Write:", context.getString(R.string.shared_preference_last_account), "→", account.getId());
+ sharedPreferencesEditor.putLong(context.getString(R.string.shared_preference_last_account), account.getId());
+ sharedPreferencesEditor.apply();
+ });
+ }
+
+ public void removeCurrentAccount() {
+ executor.submit(() -> {
+ // Glide Module depends on correct account being set.
+ // TODO Use SingleSignOnURL where possible, allow passing ssoAccountName to MarkdownEditor
+ SingleAccountHelper.setCurrentAccount(context, null);
+
+ DeckLog.log("--- Remove:", context.getString(R.string.shared_preference_last_account));
+ sharedPreferencesEditor.remove(context.getString(R.string.shared_preference_last_account));
+ sharedPreferencesEditor.apply();
+ });
+ }
+
+ public LiveData<Long> getCurrentAccountId$() {
+ return new ReactiveLiveData<>(new SharedPreferenceLongLiveData(sharedPreferences, this.context.getString(R.string.shared_preference_last_account), NOT_AVAILABLE))
+ .distinctUntilChanged()
+ .tap(accountId -> {
+ DeckLog.log("--- Read:", context.getString(R.string.shared_preference_last_account), "→", accountId);
+ if (NOT_AVAILABLE.equals(accountId)) {
+ executor.submit(this::removeCurrentAccount);
+ }
+ });
+ }
+
+ public CompletableFuture<Long> getCurrentAccountId() {
+ return supplyAsync(() -> {
+ final long accountId = sharedPreferences.getLong(context.getString(R.string.shared_preference_last_account), NOT_AVAILABLE);
+ DeckLog.log("--- Read:", context.getString(R.string.shared_preference_last_account), "→", accountId);
+
+ if (NOT_AVAILABLE.equals(accountId)) {
+ saveNeighbourOfAccount(NOT_AVAILABLE);
+ throw new CompletionException(new IllegalStateException("No current account ID set"));
+ }
+
+ return accountId;
+ }, executor);
+ }
+
+ @WorkerThread
+ public void saveNeighbourOfAccount(long currentAccountId) {
+ getAllAccountsDirectly()
+ .stream()
+ .filter(account -> currentAccountId != account.getId())
+ .findFirst()
+ .ifPresentOrElse(this::saveCurrentAccount, this::removeCurrentAccount);
+ }
+
+ @ColorInt
+ public CompletableFuture<Integer> getCurrentAccountColor(long accountId) {
+ return supplyAsync(() -> db.getAccountDao().getAccountColorDirectly(accountId), executor)
+ .thenApplyAsync(color -> color == null ? defaultColor : color, executor);
+ }
+
+ // -------------
+ // Current board
+ // -------------
+
+ public void saveCurrentBoardId(long accountId, long boardId) {
+ DeckLog.log("--- Write:", context.getString(R.string.shared_preference_last_board_for_account_) + accountId, "→", boardId);
+ sharedPreferencesEditor.putLong(context.getString(R.string.shared_preference_last_board_for_account_) + accountId, boardId);
+ sharedPreferencesEditor.apply();
+ }
+
+ public void removeCurrentBoardId(long accountId) {
+ DeckLog.log("--- Remove:", context.getString(R.string.shared_preference_last_board_for_account_) + accountId);
+ sharedPreferencesEditor.remove(context.getString(R.string.shared_preference_last_board_for_account_) + accountId);
+ sharedPreferencesEditor.apply();
+ }
+
+ public LiveData<Long> getCurrentBoardId$(long accountId) {
+ return new ReactiveLiveData<>(new SharedPreferenceLongLiveData(sharedPreferences,
+ this.context.getString(R.string.shared_preference_last_board_for_account_) + accountId, NOT_AVAILABLE))
+ .distinctUntilChanged()
+ .tap(boardId -> {
+ DeckLog.log("--- Read:", context.getString(R.string.shared_preference_last_board_for_account_) + accountId, "→", boardId);
+ if (NOT_AVAILABLE.equals(boardId)) {
+ executor.submit(() -> saveNeighbourOfBoard(accountId, NOT_AVAILABLE));
+ }
+ });
+ }
+
+ @WorkerThread
+ public void saveNeighbourOfBoard(long accountId, long currentBoardId) {
+ getNeighbour(db.getBoardDao().getNotDeletedBoardsDirectly(accountId, 0), currentBoardId)
+ .ifPresentOrElse(neighbourBoardId -> saveCurrentBoardId(accountId, neighbourBoardId), () -> removeCurrentBoardId(accountId));
+ }
+
+ public CompletableFuture<Integer> getCurrentBoardColor(long accountId, long boardId) {
+ return supplyAsync(() -> getBoardColorDirectly(accountId, boardId), executor)
+ .thenApplyAsync(color -> color == null ? defaultColor : color, executor);
+ }
+
+ // -------------
+ // Current stack
+ // -------------
+
+ public void saveCurrentStackId(long accountId, long boardId, long stackId) {
+ DeckLog.log("--- Write:", context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, "→", stackId);
+ sharedPreferencesEditor.putLong(context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, stackId);
+ sharedPreferencesEditor.apply();
+ }
+
+ public void removeCurrentStackId(long accountId, long boardId) {
+ DeckLog.log("--- Remove:", context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId);
+ sharedPreferencesEditor.remove(context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId);
+ sharedPreferencesEditor.apply();
+ }
+
+ public LiveData<Long> getCurrentStackId$(long accountId, long boardId) {
+ return new ReactiveLiveData<>(new SharedPreferenceLongLiveData(sharedPreferences, context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, NOT_AVAILABLE))
+ .distinctUntilChanged()
+ .tap(stackId -> {
+ DeckLog.log("--- Read:", context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, "→", stackId);
+ if (NOT_AVAILABLE.equals(stackId)) {
+ executor.submit(() -> saveNeighbourOfStack(accountId, boardId, NOT_AVAILABLE));
+ }
+ });
+ }
+
+ @WorkerThread
+ public void saveNeighbourOfStack(long accountId, long boardId, long currentStackId) {
+ getNeighbour(getFullStacksForBoardDirectly(accountId, boardId), currentStackId)
+ .ifPresentOrElse(
+ neighbourStackId -> saveCurrentStackId(accountId, boardId, neighbourStackId),
+ () -> removeCurrentStackId(accountId, boardId));
+ }
+
+ /**
+ * @return the local ID of the direct neighbour of the given {@param currentId} if available. Prefers neighbours to the start of the wanted, but might also return a neighbour to the end.
+ */
+ private Optional<Long> getNeighbour(List<? extends IRemoteEntity> entities, long currentId) {
+ if (entities.size() < 1) {
+ return Optional.empty();
+ }
+
+ @Nullable Integer position = null;
+
+ for (int i = 0; i < entities.size(); i++) {
+ if (entities.get(i).getLocalId() == currentId) {
+ position = i;
+ }
+ }
+
+ // Not found, but there is an entry
+ if (position == null) {
+ return Optional.of(entities.get(0).getLocalId());
+ }
+
+ // Current entity is last entity
+ if (position == 0 && entities.size() == 1) {
+ return Optional.empty();
+ }
+
+ return Optional.of(position > 0
+ ? entities.get(position - 1).getLocalId()
+ : entities.get(position + 1).getLocalId());
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
index 34a0515e3..739e4b9a4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
@@ -96,6 +96,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migra
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_28_29;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_29_30;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_30_31;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_31_32;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_8_9;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_9_10;
@@ -134,7 +135,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migra
FilterWidgetSort.class,
},
exportSchema = false,
- version = 31
+ version = 32
)
@TypeConverters({DateTypeConverter.class, EnumConverter.class})
public abstract class DeckDatabase extends RoomDatabase {
@@ -186,6 +187,7 @@ public abstract class DeckDatabase extends RoomDatabase {
.addMigrations(new Migration_28_29())
.addMigrations(new Migration_29_30(context))
.addMigrations(new Migration_30_31())
+ .addMigrations(new Migration_31_32(context))
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java
index 6ebe4a8f3..a4d1af44d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AccountDao.java
@@ -42,4 +42,10 @@ public interface AccountDao extends GenericDao<Account> {
@Query("SELECT * from account a where a.url like :hostLike and exists (select 1 from board b where b.id = :boardRemoteId and a.id = b.accountId)")
List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String hostLike, long boardRemoteId);
+
+ @Query("SELECT a.color FROM account a where a.id = :accountId")
+ LiveData<Integer> getAccountColor(long accountId);
+
+ @Query("SELECT a.color FROM account a where a.id = :accountId")
+ Integer getAccountColorDirectly(long accountId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
index 066b512e9..15daf8923 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
@@ -13,18 +13,17 @@ import it.niedermann.nextcloud.deck.model.full.FullBoard;
@Dao
public interface BoardDao extends GenericDao<Board> {
- @Query("SELECT * FROM board WHERE accountId = :accountId and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
- LiveData<List<Board>> getBoardsForAccount(final long accountId);
-
- @Query("SELECT * FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
- LiveData<List<Board>> getArchivedBoardsForAccount(final long accountId);
+ @Transaction
+ @Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
+ LiveData<List<Board>> getNotDeletedBoards(long accountId, int archived);
- @Query("SELECT * FROM board WHERE accountId = :accountId and archived = 0 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
- LiveData<List<Board>> getNonArchivedBoardsForAccount(final long accountId);
+ @Transaction
+ @Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
+ List<Board> getNotDeletedBoardsDirectly(long accountId, int archived);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = :archived and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
- LiveData<List<FullBoard>> getArchivedFullBoards(long accountId, int archived);
+ LiveData<List<FullBoard>> getNotDeletedFullBoards(long accountId, int archived);
@Query("SELECT * FROM board WHERE accountId = :accountId and id = :remoteId")
LiveData<Board> getBoardByRemoteId(final long accountId, final long remoteId);
@@ -88,4 +87,7 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT b.color FROM board b where b.localId = :localBoardId and b.accountId = :accountId")
Integer getBoardColorByLocalIdDirectly(long accountId, long localBoardId);
+
+ @Query("SELECT b.color FROM board b where b.localId = :localBoardId and b.accountId = :accountId")
+ LiveData<Integer> getBoardColor(long accountId, long localBoardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
index 671f044b0..7fad7499e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
@@ -55,7 +55,7 @@ public interface UserDao extends GenericDao<User> {
"and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm ) " +
"and u.localId <> (select b.ownerId from board b where localId = :boardId)" +
"ORDER BY u.displayname")
- List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long boardId, final String searchTerm);
+ LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long boardId, final String searchTerm);
@Query("SELECT * FROM user WHERE accountId = :accountId and uid = :uid")
User getUserByUidDirectly(final long accountId, final String uid);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_31_32.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_31_32.java
new file mode 100644
index 000000000..c26208252
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_31_32.java
@@ -0,0 +1,29 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ */
+public class Migration_31_32 extends Migration {
+
+ @NonNull
+ private final Context context;
+ public Migration_31_32(@NonNull Context context) {
+ super(31, 32);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .remove("it.niedermann.nextcloud.deck.theme_main")
+ .remove("it.niedermann.nextcloud.deck.last_account_color")
+ .apply();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java
deleted file mode 100644
index af503b029..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/LiveDataHelper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util;
-
-import static androidx.lifecycle.Transformations.distinctUntilChanged;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LifecycleOwner;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.MediatorLiveData;
-import androidx.lifecycle.Observer;
-
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-public class LiveDataHelper {
-
- private LiveDataHelper() {
- throw new UnsupportedOperationException("This class must not be instantiated.");
- }
-
- private static final ExecutorService executor = Executors.newCachedThreadPool();
-
- public static <T> LiveData<T> interceptLiveData(LiveData<T> data, DataChangeProcessor<T> onDataChange) {
- MediatorLiveData<T> ret = new MediatorLiveData<>();
-
- ret.addSource(data, changedData ->
- executor.submit(() -> {
- onDataChange.onDataChanged(changedData);
- ret.postValue(changedData);
- })
- );
- return distinctUntilChanged(ret);
- }
-
-
- public static <I, O> LiveData<O> postCustomValue(LiveData<I> data, DataTransformator<I, O> transformator) {
- final MediatorLiveData<O> ret = new MediatorLiveData<>();
- ret.addSource(data, changedData -> executor.submit(() -> ret.postValue(transformator.transform(changedData))));
- return distinctUntilChanged(ret);
- }
-
- public static <I> MediatorLiveData<I> of(I oneShot) {
- return new MediatorLiveData<>() {
- @Override
- public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) {
- super.observe(owner, observer);
- executor.submit(() -> postValue(oneShot));
- }
- };
- }
-
- public static <I, O> LiveData<O> postSingleValue(LiveData<I> data, DataTransformator<I, O> transformator) {
- final MediatorLiveData<O> ret = new MediatorLiveData<>();
- ret.addSource(data, changedData -> executor.submit(() -> ret.postValue(transformator.transform(changedData))));
- return distinctUntilChanged(ret);
- }
-
- public static <T> void observeOnce(LiveData<T> liveData, LifecycleOwner owner, Observer<T> observer) {
- final Observer<T> tempObserver = new Observer<>() {
- @Override
- public void onChanged(T result) {
- liveData.removeObserver(this);
- observer.onChanged(result);
- }
- };
- liveData.observe(owner, tempObserver);
- }
-
- public interface DataChangeProcessor<T> {
- void onDataChanged(T data);
- }
-
- public interface DataTransformator<I, O> {
- O transform(I data);
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
deleted file mode 100644
index df2eac222..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util;
-
-import androidx.annotation.Nullable;
-import androidx.lifecycle.MutableLiveData;
-
-/**
- * Extends a {@link MutableLiveData} with an error state
- *
- * @param <T>
- */
-public class WrappedLiveData<T> extends MutableLiveData<T> {
- @Nullable
- private Throwable error = null;
-
- public boolean hasError() {
- return error != null;
- }
-
- public void setError(@Nullable Throwable error) {
- this.error = error;
- }
-
- @Nullable
- public Throwable getError() {
- return error;
- }
-
- public void postError(@Nullable Throwable error) {
- setError(error);
- postValue(null);
- }
-}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
deleted file mode 100644
index b9fa58b7d..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
-
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-
-public class Debouncer <T> {
- private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1);
- private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<>();
- private final Callback<T> callback;
- private final int interval;
-
- public Debouncer(Callback<T> c, int interval) {
- this.callback = c;
- this.interval = interval;
- }
-
- public void call(T key) {
- TimerTask task = new TimerTask(key);
-
- TimerTask prev;
- do {
- prev = delayedMap.putIfAbsent(key, task);
- if (prev == null)
- sched.schedule(task, interval, TimeUnit.MILLISECONDS);
- // Exit only if new task was added to map, or existing task was extended successfully
- } while (prev != null && !prev.extend());
- }
-
- public void terminate() {
- sched.shutdownNow();
- }
-
- public interface Callback<T> {
- void call(T key);
- }
-
- // The task that wakes up when the wait time elapses
- private class TimerTask implements Runnable {
- private final T key;
- private long dueTime;
- private final Object lock = new Object();
-
- public TimerTask(T key) {
- this.key = key;
- extend();
- }
-
- public boolean extend() {
- synchronized (lock) {
- if (dueTime < 0) // Task has been shutdown
- return false;
- dueTime = System.currentTimeMillis() + interval;
- return true;
- }
- }
-
- public void run() {
- synchronized (lock) {
- long remaining = dueTime - System.currentTimeMillis();
- if (remaining > 0) { // Re-schedule task
- sched.schedule(this, remaining, TimeUnit.MILLISECONDS);
- } else { // Mark as terminated and invoke callback
- dueTime = -1;
- try {
- callback.call(key);
- } finally {
- delayedMap.remove(key);
- }
- }
- }
- }
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
deleted file mode 100644
index e0f546399..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
-
-import androidx.lifecycle.MediatorLiveData;
-
-import java.util.List;
-
-import it.niedermann.nextcloud.deck.DeckLog;
-import it.niedermann.nextcloud.deck.api.ResponseCallback;
-import it.niedermann.nextcloud.deck.exceptions.OfflineException;
-import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.model.User;
-import it.niedermann.nextcloud.deck.model.enums.DBStatus;
-import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
-import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
-
-public class UserSearchLiveData extends MediatorLiveData<List<User>> implements Debouncer.Callback<Long> {
-
- private static final int DEBOUNCE_TIME = 300; // ms
- private final DataBaseAdapter db;
- private final ServerAdapter server;
- long accountId;
- String searchTerm;
- long notYetAssignedInACL;
- private final Debouncer<Long> debouncer = new Debouncer<>(this, DEBOUNCE_TIME);
-
- public UserSearchLiveData(DataBaseAdapter db, ServerAdapter server) {
- this.db = db;
- this.server = server;
- }
-
- public UserSearchLiveData search(long accountId, long notYetAssignedInACL, String searchTerm) {
- this.accountId = accountId;
- this.searchTerm = searchTerm;
- this.notYetAssignedInACL = notYetAssignedInACL;
- new Thread(() -> debouncer.call(notYetAssignedInACL)).start();
- return this;
- }
-
-
- @Override
- public void call(Long key) {
- if (key!=notYetAssignedInACL){
- return;
- }
-
- final String term = String.copyValueOf(searchTerm.toCharArray());
-
- postCurrentFromDB(term);
-
- if (server.hasInternetConnection()) {
- try {
- Account account = db.getAccountByIdDirectly(accountId);
- server.searchUser(term, new ResponseCallback<>(account) {
- @Override
- public void onResponse(OcsUserList response) {
- if (response == null || response.getUsers().isEmpty()){
- return;
- }
- for (OcsUser user : response.getUsers()) {
- User existingUser = db.getUserByUidDirectly(accountId, user.getId());
- if (existingUser == null) {
- User newUser = new User();
- newUser.setStatus(DBStatus.UP_TO_DATE.getId());
- newUser.setPrimaryKey(user.getId());
- newUser.setUid(user.getId());
- newUser.setDisplayname(user.getDisplayName());
- db.createUser(accountId, newUser);
- }
- }
- if (!term.equals(searchTerm)) {
- return;
- }
- postCurrentFromDB(term);
- }
-
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- }
- });
- } catch (OfflineException e) {
- DeckLog.logError(e);
- }
- }
- }
-
- private void postCurrentFromDB(String term) {
- List<User> foundInDB = db.searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedInACL, term);
- postValue(foundInDB);
- }
-}