From e23e826945923ab893dce1330c5e2f71cbb6e062 Mon Sep 17 00:00:00 2001 From: Stefan Niedermann Date: Thu, 18 Nov 2021 13:59:47 +0100 Subject: #1165 Refactor PushNotifications Signed-off-by: Stefan Niedermann --- .../deck/persistence/sync/SyncManager.java | 30 +++- .../sync/adapters/db/DataBaseAdapter.java | 22 ++- .../persistence/sync/adapters/db/dao/BoardDao.java | 7 + .../deck/ui/PushNotificationActivity.java | 165 ++++++--------------- .../deck/ui/PushNotificationViewModel.java | 102 ++++++++++--- 5 files changed, 177 insertions(+), 149 deletions(-) (limited to 'app/src/main') diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java index 439ad5040..7ce19fc16 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java @@ -13,6 +13,7 @@ import androidx.annotation.ColorInt; import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.UiThread; import androidx.annotation.WorkerThread; import androidx.lifecycle.LiveData; import androidx.lifecycle.MediatorLiveData; @@ -130,8 +131,8 @@ public class SyncManager { } @AnyThread - public LiveData getLocalBoardIdByCardRemoteIdAndAccount(long cardRemoteId, @NonNull Account account) { - return dataBaseAdapter.getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, account.getId()); + public Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { + return dataBaseAdapter.getBoardByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId); } @WorkerThread @@ -168,9 +169,9 @@ public class SyncManager { } @AnyThread - public void synchronizeBoard(@NonNull ResponseCallback responseCallback, long localBoadId) { + public void synchronizeBoard(long localBoardId, @NonNull ResponseCallback responseCallback) { executor.submit(() -> { - FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(responseCallback.getAccount().getId(), localBoadId); + FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(responseCallback.getAccount().getId(), localBoardId); try { syncHelperFactory.create(serverAdapter, dataBaseAdapter, null) .setResponseCallback(responseCallback) @@ -361,7 +362,7 @@ public class SyncManager { }); } - @AnyThread + @UiThread public LiveData readAccount(long id) { return dataBaseAdapter.readAccount(id); } @@ -371,12 +372,17 @@ public class SyncManager { return dataBaseAdapter.readAccountDirectly(id); } - @AnyThread + @WorkerThread + public Account readAccountDirectly(@Nullable String name) { + return dataBaseAdapter.readAccountDirectly(name); + } + + @UiThread public LiveData readAccount(@Nullable String name) { return dataBaseAdapter.readAccount(name); } - @AnyThread + @UiThread public LiveData> readAccounts() { return dataBaseAdapter.readAccounts(); } @@ -1570,6 +1576,11 @@ public class SyncManager { return dataBaseAdapter.getBoardByRemoteId(accountId, remoteId); } + @WorkerThread + public Board getBoardByRemoteIdDirectly(long accountId, long remoteId) { + return dataBaseAdapter.getBoardByRemoteIdDirectly(accountId, remoteId); + } + public LiveData getStackByRemoteId(long accountId, long localBoardId, long remoteId) { return dataBaseAdapter.getStackByRemoteId(accountId, localBoardId, remoteId); } @@ -1578,6 +1589,11 @@ public class SyncManager { return dataBaseAdapter.getCardByRemoteID(accountId, remoteId); } + @WorkerThread + public Card getCardByRemoteIDDirectly(long accountId, long remoteId) { + return dataBaseAdapter.getCardByRemoteIDDirectly(accountId, remoteId); + } + public long createUser(long accountId, User user) { return dataBaseAdapter.createUser(accountId, user); } 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 703d50730..cb31ff0b1 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,5 +1,7 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db; +import static androidx.lifecycle.Transformations.distinctUntilChanged; + import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.Intent; @@ -78,8 +80,6 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHe import it.niedermann.nextcloud.deck.ui.upcomingcards.UpcomingCardsAdapterItem; import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget; -import static androidx.lifecycle.Transformations.distinctUntilChanged; - public class DataBaseAdapter { @NonNull @@ -124,6 +124,7 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getBoardDao().getBoardByRemoteId(accountId, remoteId)); } + @WorkerThread public Board getBoardByRemoteIdDirectly(long accountId, long remoteId) { return db.getBoardDao().getBoardByRemoteIdDirectly(accountId, remoteId); } @@ -157,6 +158,11 @@ public class DataBaseAdapter { return distinctUntilChanged(db.getCardDao().getCardByRemoteId(accountId, remoteId)); } + @WorkerThread + public Card getCardByRemoteIDDirectly(long accountId, long remoteId) { + return db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteId); + } + public FullCard getFullCardByRemoteIdDirectly(long accountId, long remoteId) { FullCard card = db.getCardDao().getFullCardByRemoteIdDirectly(accountId, remoteId); filterRelationsForCard(card); @@ -570,6 +576,13 @@ public class DataBaseAdapter { return db.getAccountDao().getAccountByIdDirectly(id); } + @WorkerThread + public Account readAccountDirectly(@Nullable String name) { + final var account = db.getAccountDao().getAccountByNameDirectly(name); + account.setUserDisplayName(db.getUserDao().getUserNameByUidDirectly(account.getId(), account.getUserName())); + return account; + } + public LiveData> getBoards(long accountId) { return distinctUntilChanged(db.getBoardDao().getBoardsForAccount(accountId)); @@ -1099,6 +1112,11 @@ public class DataBaseAdapter { return db.getBoardDao().getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, accountId); } + @WorkerThread + public Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId) { + return db.getBoardDao().getBoardByAccountAndCardRemoteIdDirectly(accountId, cardRemoteId); + } + @WorkerThread public void countCardsInStackDirectly(long accountId, long localStackId, @NonNull IResponseCallback callback) { callback.onResponse(db.getCardDao().countCardsInStackDirectly(accountId, localStackId)); 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 89ada6e18..89846ac39 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 @@ -75,6 +75,13 @@ public interface BoardDao extends GenericDao { "WHERE c.id = :cardRemoteId and c.accountId = :accountId") LiveData getLocalBoardIdByCardRemoteIdAndAccountId(long cardRemoteId, long accountId); + @Query("SELECT b.* " + + "FROM card c " + + "inner join stack s on s.localId = c.stackId " + + "inner join board b on s.boardId = b.localId " + + "WHERE c.id = :cardRemoteId and c.accountId = :accountId") + Board getBoardByAccountAndCardRemoteIdDirectly(long accountId, long cardRemoteId); + @Query("SELECT count(*) FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3") LiveData countArchivedBoards(long accountId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java index 59f01f862..25a255aa1 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java @@ -1,7 +1,5 @@ package it.niedermann.nextcloud.deck.ui; -import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce; - import android.content.Intent; import android.net.Uri; import android.text.TextUtils; @@ -13,16 +11,19 @@ import androidx.annotation.UiThread; import androidx.appcompat.app.AppCompatActivity; import androidx.lifecycle.ViewModelProvider; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import it.niedermann.android.util.ColorUtil; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; -import it.niedermann.nextcloud.deck.api.ResponseCallback; +import it.niedermann.nextcloud.deck.api.IResponseCallback; import it.niedermann.nextcloud.deck.databinding.ActivityPushNotificationBinding; import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.ui.card.EditActivity; import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment; import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler; -import it.niedermann.nextcloud.deck.util.ProjectUtil; +import kotlin.Triple; /** * Warning: Do not move this class to another package or folder! @@ -32,6 +33,7 @@ public class PushNotificationActivity extends AppCompatActivity { private ActivityPushNotificationBinding binding; private PushNotificationViewModel viewModel; + private final ExecutorService executor = Executors.newSingleThreadExecutor(); // Provided by Files app NotificationJob private static final String KEY_SUBJECT = "subject"; @@ -66,107 +68,38 @@ public class PushNotificationActivity extends AppCompatActivity { binding.message.setVisibility(View.VISIBLE); } - final String link = intent.getStringExtra(KEY_LINK); - try { - final long[] ids = ProjectUtil.extractBoardIdAndCardIdFromUrl(link); - - binding.cancel.setOnClickListener((v) -> finish()); - final String accountString = intent.getStringExtra(KEY_ACCOUNT); - - if (ids.length == 2) { - final String cardRemoteIdString = intent.getExtras().getString(KEY_CARD_REMOTE_ID, String.valueOf(ids[1])); - DeckLog.verbose("cardRemoteIdString = ", cardRemoteIdString); - try { - final int cardRemoteId = Integer.parseInt(cardRemoteIdString); - observeOnce(viewModel.readAccount(accountString), this, (account -> { - if (account != null) { - viewModel.setAccount(account.getName()); - DeckLog.verbose("account:", account); - observeOnce(viewModel.getBoardByRemoteId(account.getId(), ids[0]), PushNotificationActivity.this, (board -> { - DeckLog.verbose("BoardLocalId:", board); - if (board != null) { - observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> { - DeckLog.verbose("Card:", card); - if (card != null) { - viewModel.synchronizeCard(new ResponseCallback<>(account) { - @Override - public void onResponse(Boolean response) { - openCardOnSubmit(account, board.getLocalId(), card.getLocalId()); - } - - @Override - public void onError(Throwable throwable) { - super.onError(throwable); - openCardOnSubmit(account, board.getLocalId(), card.getLocalId()); - } - }, card); - } else { - DeckLog.info("Card is not yet available locally. Synchronize board with localId", board); - - viewModel.synchronizeBoard(new ResponseCallback<>(account) { - @Override - public void onResponse(Boolean response) { - runOnUiThread(() -> observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> { - DeckLog.verbose("Card:", card); - if (card != null) { - openCardOnSubmit(account, board.getLocalId(), card.getLocalId()); - } else { - DeckLog.warn("Something went wrong while synchronizing the card", cardRemoteId, " (cardRemoteId). Given fullCard is null."); - applyBrandToSubmitButton(account); - fallbackToBrowser(link); - } - }))); - } - - @Override - public void onError(Throwable throwable) { - super.onError(throwable); - DeckLog.warn("Something went wrong while synchronizing the board with localId", board); - applyBrandToSubmitButton(account); - fallbackToBrowser(link); - } - }, board.getLocalId()); - } - })); - } else { - DeckLog.warn("Given localBoardId for cardRemoteId", cardRemoteId, "is null."); - applyBrandToSubmitButton(account); - fallbackToBrowser(link); - } - })); - } else { - DeckLog.warn("Given account for", accountString, "is null."); - fallbackToBrowser(link); - } - })); - } catch (NumberFormatException e) { - DeckLog.logError(e); - fallbackToBrowser(link); - } - } else { - DeckLog.warn("Link does not contain two IDs (expected one board id and one card id):", link); - fallbackToBrowser(link); - } - } catch (Throwable t) { - final String params = "Error while receiving push notification:\n" - + KEY_SUBJECT + ": [" + intent.getStringExtra(KEY_SUBJECT) + "]\n" - + KEY_MESSAGE + ": [" + intent.getStringExtra(KEY_MESSAGE) + "]\n" - + KEY_LINK + ": [" + intent.getStringExtra(KEY_LINK) + "]\n" - + KEY_CARD_REMOTE_ID + ": [" + intent.getStringExtra(KEY_CARD_REMOTE_ID) + "]\n" - + KEY_ACCOUNT + ": [" + intent.getStringExtra(KEY_ACCOUNT) + "]"; - ExceptionDialogFragment.newInstance(new Exception(params, t), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); - DeckLog.logError(t); - } + binding.cancel.setOnClickListener((v) -> finish()); + viewModel.getAccount().observe(this, this::applyBrandToSubmitButton); + executor.submit(() -> viewModel.getCardInformation( + intent.getStringExtra(KEY_ACCOUNT), + intent.getStringExtra(KEY_CARD_REMOTE_ID), + new IResponseCallback<>() { + @Override + public void onResponse(Triple response) { + runOnUiThread(() -> openCardOnSubmit(response.getFirst(), response.getSecond(), response.getThird())); + } + + @Override + public void onError(Throwable throwable) { + IResponseCallback.super.onError(throwable); + runOnUiThread(() -> fallbackToBrowser(intent.getStringExtra(KEY_LINK))); + final String params = "Error while receiving push notification:\n" + + KEY_SUBJECT + ": [" + intent.getStringExtra(KEY_SUBJECT) + "]\n" + + KEY_MESSAGE + ": [" + intent.getStringExtra(KEY_MESSAGE) + "]\n" + + KEY_LINK + ": [" + intent.getStringExtra(KEY_LINK) + "]\n" + + KEY_CARD_REMOTE_ID + ": [" + intent.getStringExtra(KEY_CARD_REMOTE_ID) + "]\n" + + KEY_ACCOUNT + ": [" + intent.getStringExtra(KEY_ACCOUNT) + "]"; + ExceptionDialogFragment.newInstance(new Exception(params, throwable), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()); + } + })); } - private void openCardOnSubmit(@NonNull Account account, long boardLocalId, long cardlocalId) { - runOnUiThread(() -> { - binding.submit.setOnClickListener((v) -> launchEditActivity(account, boardLocalId, cardlocalId)); - binding.submit.setText(R.string.simple_open); - applyBrandToSubmitButton(account); - binding.submit.setEnabled(true); - binding.progress.setVisibility(View.INVISIBLE); - }); + private void openCardOnSubmit(@NonNull Account account, long boardLocalId, long cardLocalId) { + binding.submit.setOnClickListener((v) -> launchEditActivity(account, boardLocalId, cardLocalId)); + binding.submit.setText(R.string.simple_open); + applyBrandToSubmitButton(account.getColor()); + binding.submit.setEnabled(true); + binding.progress.setVisibility(View.INVISIBLE); } /** @@ -174,20 +107,15 @@ public class PushNotificationActivity extends AppCompatActivity { */ private void fallbackToBrowser(String link) { DeckLog.warn("Falling back to browser as notification handler."); - runOnUiThread(() -> { - try { - final Uri uri = Uri.parse(link); - binding.submit.setOnClickListener((v) -> { - Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri); - startActivity(browserIntent); - }); - binding.submit.setText(R.string.open_in_browser); - binding.submit.setEnabled(true); - binding.progress.setVisibility(View.INVISIBLE); - } catch (Throwable t) { - DeckLog.logError(t); - } - }); + try { + final var uri = Uri.parse(link); + binding.submit.setOnClickListener((v) -> startActivity(new Intent(Intent.ACTION_VIEW, uri))); + binding.submit.setText(R.string.open_in_browser); + binding.submit.setEnabled(true); + binding.progress.setVisibility(View.INVISIBLE); + } catch (Throwable t) { + DeckLog.logError(t); + } } @UiThread @@ -205,8 +133,7 @@ public class PushNotificationActivity extends AppCompatActivity { // TODO implement Branded interface // TODO apply branding based on board color - public void applyBrandToSubmitButton(@NonNull Account account) { - @ColorInt final int mainColor = account.getColor(); + public void applyBrandToSubmitButton(@ColorInt int mainColor) { try { binding.submit.setBackgroundColor(mainColor); binding.submit.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(mainColor)); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java index ff640274c..42ef05d0c 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java @@ -1,52 +1,112 @@ package it.niedermann.nextcloud.deck.ui; +import static androidx.lifecycle.Transformations.distinctUntilChanged; +import static androidx.lifecycle.Transformations.map; + +import android.annotation.SuppressLint; import android.app.Application; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import com.nextcloud.android.sso.helper.SingleAccountHelper; +import it.niedermann.nextcloud.deck.DeckLog; +import it.niedermann.nextcloud.deck.api.IResponseCallback; import it.niedermann.nextcloud.deck.api.ResponseCallback; import it.niedermann.nextcloud.deck.model.Account; -import it.niedermann.nextcloud.deck.model.Board; -import it.niedermann.nextcloud.deck.model.Card; import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; +import kotlin.Triple; public class PushNotificationViewModel extends AndroidViewModel { private final SyncManager readAccountSyncManager; - private SyncManager accountSpecificSyncManager; + private final MutableLiveData account = new MutableLiveData<>(); public PushNotificationViewModel(@NonNull Application application) { super(application); this.readAccountSyncManager = new SyncManager(application); } - public LiveData readAccount(@Nullable String name) { - return readAccountSyncManager.readAccount(name); - } - - public void setAccount(@NonNull String accountName) { - SingleAccountHelper.setCurrentAccount(getApplication(), accountName); - accountSpecificSyncManager = new SyncManager(getApplication()); - } + @WorkerThread + public void getCardInformation( + @Nullable String accountString, + @Nullable String cardRemoteIdString, + @NonNull IResponseCallback> callback) { + if (cardRemoteIdString == null) { + callback.onError(new NumberFormatException("cardRemoteIdString is null")); + return; + } + try { + final long cardRemoteId; + try { + DeckLog.verbose("cardRemoteIdString = ", cardRemoteIdString); + cardRemoteId = Long.parseLong(cardRemoteIdString); + } catch (NumberFormatException e) { + callback.onError(e); + return; + } + final var account = readAccountSyncManager.readAccountDirectly(accountString); + if (account == null) { + callback.onError(new RuntimeException("Given account for" + accountString + "is null.")); + return; + } + SingleAccountHelper.setCurrentAccount(getApplication(), account.getName()); + final var syncManager = new SyncManager(getApplication()); + DeckLog.verbose("account:", account); + final var board = syncManager.getBoardByAccountAndCardRemoteIdDirectly(account.getId(), cardRemoteId); + DeckLog.verbose("BoardLocalId:", board); + if (board == null) { + callback.onError(new RuntimeException("Given localBoardId for cardRemoteId" + cardRemoteId + "is null.")); + return; + } + final var card = syncManager.getCardByRemoteIDDirectly(account.getId(), cardRemoteId); + DeckLog.verbose("Card:", card); + if (card != null) { + syncManager.synchronizeCard(new ResponseCallback<>(account) { + @Override + public void onResponse(Boolean response) { + callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + } - public LiveData getBoardByRemoteId(long accountId, long remoteId) { - return accountSpecificSyncManager.getBoardByRemoteId(accountId, remoteId); - } + @Override + public void onError(Throwable throwable) { + super.onError(throwable); + callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + } + }, card); + } else { + DeckLog.info("Card is not yet available locally. Synchronize board with localId", board); - public LiveData getCardByRemoteID(long accountId, long remoteId) { - return accountSpecificSyncManager.getCardByRemoteID(accountId, remoteId); - } + syncManager.synchronizeBoard(board.getLocalId(), new ResponseCallback<>(account) { + @Override + public void onResponse(Boolean response) { + final var card = syncManager.getCardByRemoteIDDirectly(account.getId(), cardRemoteId); + DeckLog.verbose("Card:", card); + if (card != null) { + callback.onResponse(new Triple<>(account, board.getLocalId(), card.getLocalId())); + } else { + callback.onError(new RuntimeException("Something went wrong while synchronizing the card" + cardRemoteId + " (cardRemoteId). Given fullCard is null.")); + } + } - public void synchronizeCard(@NonNull ResponseCallback responseCallback, Card card) { - accountSpecificSyncManager.synchronizeCard(responseCallback, card); + @SuppressLint("MissingSuperCall") + @Override + public void onError(Throwable throwable) { + callback.onError(new RuntimeException("Something went wrong while synchronizing the board with localId" + board)); + } + }); + } + } catch (Throwable t) { + callback.onError(t); + } } - public void synchronizeBoard(@NonNull ResponseCallback responseCallback, long localBoadId) { - accountSpecificSyncManager.synchronizeBoard(responseCallback, localBoadId); + public LiveData getAccount() { + return distinctUntilChanged(map(this.account, Account::getColor)); } } -- cgit v1.2.3