diff options
author | Stefan Niedermann <info@niedermann.it> | 2023-03-01 14:43:53 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2023-03-09 11:53:19 +0300 |
commit | 3ea462ca9e2ae18ba9d869125da8d8d07f2c7854 (patch) | |
tree | 30257c67768325d5972ec499a6eb41e11017ac6d /app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters | |
parent | bfab286b0bc6dbfac1211eec64d74b66b2ce1e6d (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')
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); - } -} |