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>2020-05-14 13:45:37 +0300
committerStefan Niedermann <info@niedermann.it>2020-05-14 13:45:37 +0300
commit700cf960612d8ea157e65bb5c0ca053b5fa3a5f3 (patch)
treef9fb67829438af08b11befc73c21bf219f459179 /app/src/main/java/it/niedermann/nextcloud
parent6f2580db3ca95e27533eebd6f3b8cd17271f1120 (diff)
parent06710dbac904ce33ae6d7b4c84499159d181b120 (diff)
Merge branch 'master' into 454-replay-to-comments
# Conflicts: # app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java # app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullSingleCardWidgetModel.java44
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/singlecard/SingleCardWidgetModel.java86
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java38
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java67
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java153
19 files changed, 550 insertions, 44 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
index 4a31ad54f..1b6d517a6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
@@ -344,7 +344,9 @@ public class JsonToEntityParser {
JsonObject deck = caps.getAsJsonObject("deck");
if (deck.has("version")) {
version = deck.get("version").getAsString();
-
+ if (version == null || version.trim().length() < 1) {
+ throw new IllegalArgumentException("capabilities endpoint returned an invalid version string: \""+version+"\"");
+ }
}
}
if (caps.has("theming")) {
@@ -353,6 +355,9 @@ public class JsonToEntityParser {
capabilities.setTextColor(theming.get("color-text").getAsString());
}
}
+ if (version == null || version.trim().length() < 1) {
+ throw new IllegalArgumentException("capabilities endpoint returned no version string at all!");
+ }
capabilities.setDeckVersion(Version.of(version));
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullSingleCardWidgetModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullSingleCardWidgetModel.java
new file mode 100644
index 000000000..c9786f4d2
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullSingleCardWidgetModel.java
@@ -0,0 +1,44 @@
+package it.niedermann.nextcloud.deck.model.full;
+
+import androidx.room.Embedded;
+import androidx.room.Ignore;
+import androidx.room.Relation;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
+
+public class FullSingleCardWidgetModel {
+
+ @Embedded
+ private SingleCardWidgetModel model;
+
+ @Relation(parentColumn = "accountId", entityColumn = "id")
+ private Account account;
+
+ @Ignore
+ private FullCard fullCard;
+
+ public SingleCardWidgetModel getModel() {
+ return model;
+ }
+
+ public void setModel(SingleCardWidgetModel model) {
+ this.model = model;
+ }
+
+ public Account getAccount() {
+ return account;
+ }
+
+ public void setAccount(Account account) {
+ this.account = account;
+ }
+
+ public FullCard getFullCard() {
+ return fullCard;
+ }
+
+ public void setFullCard(FullCard fullCard) {
+ this.fullCard = fullCard;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
index 5e7169472..701cdd335 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
@@ -80,7 +80,7 @@ public class Version implements Comparable<Version> {
if (matcher.find()) {
return Integer.parseInt(matcher.group());
}
- return 0;
+ throw new IllegalArgumentException("could not extract a number from following string: \""+containsNumbers+"\"");
}
@NonNull
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/singlecard/SingleCardWidgetModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/singlecard/SingleCardWidgetModel.java
new file mode 100644
index 000000000..fcaab380b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/singlecard/SingleCardWidgetModel.java
@@ -0,0 +1,86 @@
+package it.niedermann.nextcloud.deck.model.widget.singlecard;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Card;
+
+@Entity(
+ indices = {
+ @Index("cardId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Account.class,
+ parentColumns = "id",
+ childColumns = "accountId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = Board.class,
+ parentColumns = "localId",
+ childColumns = "boardId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = Card.class,
+ parentColumns = "localId",
+ childColumns = "cardId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class SingleCardWidgetModel {
+ @Ignore
+ private static final long serialVersionUID = 0;
+
+ @PrimaryKey()
+ private Integer widgetId;
+ private Long accountId;
+ private Long boardId;
+ private Long cardId;
+
+ public Integer getWidgetId() {
+ return widgetId;
+ }
+
+ public void setWidgetId(Integer widgetId) {
+ this.widgetId = widgetId;
+ }
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public Long getBoardId() {
+ return boardId;
+ }
+
+ public void setBoardId(Long boardId) {
+ this.boardId = boardId;
+ }
+
+ public Long getCardId() {
+ return cardId;
+ }
+
+ public void setCardId(Long cardId) {
+ this.cardId = cardId;
+ }
+
+ @Override
+ public String toString() {
+ return "SingleCardWidget{" +
+ "widgetId=" + widgetId +
+ ", accountId=" + accountId +
+ ", boardId=" + boardId +
+ ", cardId=" + cardId +
+ '}';
+ }
+}
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 5722f0f1e..29f7725b8 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
@@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteConstraintException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
@@ -16,6 +17,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
+import java.util.NoSuchElementException;
import java.util.concurrent.CountDownLatch;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -35,6 +37,7 @@ import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.internal.FilterInformation;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
@@ -1281,6 +1284,30 @@ public class SyncManager {
return liveData;
}
+ // -------------------
+ // Widgets
+ // -------------------
+
+ /**
+ * Can be called from a configuration screen or a picker.
+ * Creates a new entry in the database, if row with given widgetId does not yet exist.
+ */
+ public void addOrUpdateSingleCardWidget(int widgetId, long accountId, long boardId, long localCardId) {
+ doAsync(() -> dataBaseAdapter.createSingleCardWidget(widgetId, accountId, boardId, localCardId));
+ }
+
+ @WorkerThread
+ public FullSingleCardWidgetModel getSingleCardWidgetModelDirectly(int widgetId) throws NoSuchElementException {
+ final FullSingleCardWidgetModel model = dataBaseAdapter.getFullSingleCardWidgetModel(widgetId);
+ if (model == null) {
+ throw new NoSuchElementException();
+ }
+ return model;
+ }
+
+ public void deleteSingleCardWidgetModel(int widgetId) {
+ doAsync(() -> dataBaseAdapter.deleteSingleCardWidget(widgetId));
+ }
private static class BooleanResultHolder {
public boolean result = true;
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 821d910ab..37d2660e5 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
@@ -12,6 +12,7 @@ import java.util.ArrayList;
import java.util.Date;
import java.util.List;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -27,6 +28,7 @@ import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
@@ -34,9 +36,11 @@ import it.niedermann.nextcloud.deck.model.internal.FilterInformation;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
+import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
+import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
@@ -174,7 +178,7 @@ public class DataBaseAdapter {
return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardsForStack(accountId, localStackId), this::filterRelationsForCard);
}
- List<Object> args = new ArrayList();
+ List<Object> args = new ArrayList<>();
StringBuilder query = new StringBuilder("SELECT * FROM card c " +
"WHERE accountId = ? AND stackId = ? ");
args.add(accountId);
@@ -488,6 +492,10 @@ public class DataBaseAdapter {
public void updateCard(Card card, boolean setStatus) {
markAsEditedIfNeeded(card, setStatus);
db.getCardDao().update(card);
+ if (db.getSingleCardWidgetModelDao().containsCardLocalId(card.getLocalId())) {
+ DeckLog.info("Notifying widget about card changes for \"" + card.getTitle() + "\"");
+ SingleCardWidget.notifyDatasetChanged(context);
+ }
}
public long createAccessControl(long accountId, AccessControl entity) {
@@ -829,4 +837,32 @@ public class DataBaseAdapter {
public Long getLocalCommentIdForRemoteIdDirectly(long accountId, Long remoteCommentId) {
return db.getCommentDao().getLocalCommentIdForRemoteIdDirectly(accountId, remoteCommentId);
}
+
+
+ // -------------------
+ // Widgets
+ // -------------------
+
+ public long createSingleCardWidget(int widgetId, long accountId, long boardLocalId, long cardLocalId) {
+ SingleCardWidgetModel model = new SingleCardWidgetModel();
+ model.setWidgetId(widgetId);
+ model.setAccountId(accountId);
+ model.setBoardId(boardLocalId);
+ model.setCardId(cardLocalId);
+ return db.getSingleCardWidgetModelDao().insert(model);
+ }
+
+ public FullSingleCardWidgetModel getFullSingleCardWidgetModel(int widgetId) {
+ FullSingleCardWidgetModel model = db.getSingleCardWidgetModelDao().getFullCardByRemoteIdDirectly(widgetId);
+ if (model != null) {
+ model.setFullCard(db.getCardDao().getFullCardByLocalIdDirectly(model.getAccount().getId(), model.getModel().getCardId()));
+ }
+ return model;
+ }
+
+ public void deleteSingleCardWidget(int widgetId) {
+ SingleCardWidgetModel model = new SingleCardWidgetModel();
+ model.setWidgetId(widgetId);
+ db.getSingleCardWidgetModelDao().delete(model);
+ }
}
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 3e2b6dec4..a87a60cd2 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
@@ -31,6 +31,7 @@ import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
+import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccessControlDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccountDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.ActivityDao;
@@ -46,6 +47,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.JoinCardWit
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.LabelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.MentionDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.PermissionDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.SingleCardWidgetModelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.StackDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
@@ -68,6 +70,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
Activity.class,
DeckComment.class,
Mention.class,
+ SingleCardWidgetModel.class,
},
exportSchema = false,
version = 12
@@ -149,6 +152,14 @@ public abstract class DeckDatabase extends RoomDatabase {
private static final Migration MIGRATION_11_12 = new Migration(11, 12) {
@Override
public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `SingleCardWidgetModel` (`widgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `boardId` INTEGER, `cardId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_SingleCardWidgetModel_cardId` ON `SingleCardWidgetModel` (`cardId`)");
+ }
+ };
+
+ private static final Migration MIGRATION_12_13 = new Migration(11, 12) {
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE `DeckComment` ADD `parentId` INTEGER REFERENCES DeckComment(localId) ON DELETE CASCADE");
database.execSQL("CREATE INDEX `idx_comment_parentID` ON DeckComment(parentId)");
}
@@ -180,12 +191,14 @@ public abstract class DeckDatabase extends RoomDatabase {
.addMigrations(MIGRATION_9_10)
.addMigrations(MIGRATION_10_11)
.addMigrations(MIGRATION_11_12)
+ .addMigrations(MIGRATION_12_13)
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
}
public abstract AccountDao getAccountDao();
+
public abstract AccessControlDao getAccessControlDao();
public abstract BoardDao getBoardDao();
@@ -217,4 +230,6 @@ public abstract class DeckDatabase extends RoomDatabase {
public abstract CommentDao getCommentDao();
public abstract MentionDao getMentionDao();
+
+ public abstract SingleCardWidgetModelDao getSingleCardWidgetModelDao();
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
index e58f2c819..d63f98b6a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
@@ -33,7 +33,7 @@ public interface CardDao extends GenericDao<Card> {
FullCard getFullCardByLocalIdDirectly(final long accountId, final long localId);
@Transaction // v not deleted!
- @Query("SELECT * FROM card WHERE accountId = :accountId AND stackId = :localStackId and status<>3 order by `order`, createdAt asc")
+ @Query("SELECT * FROM card WHERE accountId = :accountId AND archived = 0 AND stackId = :localStackId and status<>3 order by `order`, createdAt asc")
LiveData<List<FullCard>> getFullCardsForStack(final long accountId, final long localStackId);
@Transaction // v not deleted!
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java
new file mode 100644
index 000000000..0c2a485c1
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java
@@ -0,0 +1,20 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+import androidx.room.Transaction;
+
+import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
+import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
+
+@Dao
+public interface SingleCardWidgetModelDao extends GenericDao<SingleCardWidgetModel> {
+
+ @Transaction
+ @Query("SELECT * FROM singlecardwidgetmodel WHERE widgetId = :widgetId")
+ FullSingleCardWidgetModel getFullCardByRemoteIdDirectly(final int widgetId);
+
+ @Transaction
+ @Query("SELECT EXISTS (SELECT 1 FROM singlecardwidgetmodel WHERE cardId = :cardLocalId)")
+ boolean containsCardLocalId(final Long cardLocalId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
index f4d744f11..6d2623a91 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
@@ -364,8 +364,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
protected void onStop() {
// Clean up zombie fragments in case of system initiated process death.
// See linked issues in https://github.com/stefan-niedermann/nextcloud-deck/issues/478
- for (Fragment fragment : getSupportFragmentManager().getFragments()) {
- getSupportFragmentManager().beginTransaction().remove(fragment).commit();
+ try {
+ for (Fragment fragment : getSupportFragmentManager().getFragments()) {
+ getSupportFragmentManager().beginTransaction().remove(fragment).commit();
+ }
+ } catch (IllegalStateException e) {
+ DeckLog.warn("onSAveInstanceState has already been called.");
}
super.onStop();
}
@@ -411,8 +415,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
public void onUpdateStack(long localStackId, String stackName) {
observeOnce(syncManager.getStack(mainViewModel.getCurrentAccount().getId(), localStackId), MainActivity.this, fullStack -> {
fullStack.getStack().setTitle(stackName);
- // TODO error handling
- syncManager.updateStack(fullStack);
+ final WrappedLiveData<FullStack> archiveLiveData = syncManager.updateStack(fullStack);
+ observeOnce(archiveLiveData, this, (v) -> {
+ if (archiveLiveData.hasError()) {
+ ExceptionDialogFragment.newInstance(archiveLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
});
}
@@ -974,9 +982,13 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
public void onStackDeleted(Long stackLocalId) {
long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
observeOnce(syncManager.getStack(mainViewModel.getCurrentAccount().getId(), stackId), MainActivity.this, fullStack -> {
- DeckLog.log("Delete stack #" + fullStack.getLocalId() + ": " + fullStack.getStack().getTitle());
- // TODO error handling
- syncManager.deleteStack(fullStack.getStack());
+ DeckLog.info("Delete stack #" + fullStack.getLocalId() + ": " + fullStack.getStack().getTitle());
+ final WrappedLiveData<Void> deleteStackLiveData = syncManager.deleteStack(fullStack.getStack());
+ observeOnce(deleteStackLiveData, this, (v) -> {
+ if (deleteStackLiveData.hasError()) {
+ ExceptionDialogFragment.newInstance(deleteStackLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
});
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
index e8dd9b8dd..00500ae97 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
@@ -55,7 +55,7 @@ public class ArchivedCardsActvitiy extends BrandedActivity {
syncManager = new SyncManager(this);
- adapter = new ArchivedCardsAdapter(this, account, boardId, false, syncManager, this);
+ adapter = new ArchivedCardsAdapter(this, getSupportFragmentManager(), account, boardId, false, syncManager, this);
binding.recyclerView.setAdapter(adapter);
syncManager.getArchivedFullCardsForBoard(account.getId(), boardId).observe(this, (fullCards) -> {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
index 955749dd0..80502ef38 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
@@ -7,6 +7,7 @@ import android.view.View;
import android.widget.PopupMenu;
import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import org.jetbrains.annotations.NotNull;
@@ -21,8 +22,8 @@ import it.niedermann.nextcloud.deck.ui.card.ItemCardViewHolder;
public class ArchivedCardsAdapter extends CardAdapter {
@SuppressWarnings("WeakerAccess")
- public ArchivedCardsAdapter(@NonNull Context context, @NonNull Account account, long boardId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner) {
- super(context, account, boardId, 0L, canEdit, syncManager, lifecycleOwner, null);
+ public ArchivedCardsAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull Account account, long boardId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner) {
+ super(context, fragmentManager, account, boardId, 0L, canEdit, syncManager, lifecycleOwner, null);
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
index f9b8a46de..15902bd14 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
@@ -14,6 +14,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
@@ -39,16 +40,21 @@ import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.branding.Branded;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.util.DateUtil;
import it.niedermann.nextcloud.deck.util.ViewUtil;
+import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
+
public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implements DragAndDropAdapter<FullCard>, Branded {
protected final SyncManager syncManager;
+ private final FragmentManager fragmentManager;
private final Account account;
private final long boardId;
private final long stackId;
@@ -64,8 +70,9 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem
private int mainColor;
- public CardAdapter(@NonNull Context context, @NonNull Account account, long boardId, long stackId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner, @Nullable SelectCardListener selectCardListener) {
+ public CardAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull Account account, long boardId, long stackId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner, @Nullable SelectCardListener selectCardListener) {
this.context = context;
+ this.fragmentManager = fragmentManager;
this.lifecycleOwner = lifecycleOwner;
this.account = account;
this.boardId = boardId;
@@ -296,13 +303,21 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem
return true;
}
case R.id.action_card_archive: {
- // TODO error handling
- syncManager.archiveCard(fullCard);
+ final WrappedLiveData<FullCard> archiveLiveData = syncManager.archiveCard(fullCard);
+ observeOnce(archiveLiveData, lifecycleOwner, (v) -> {
+ if (archiveLiveData.hasError()) {
+ ExceptionDialogFragment.newInstance(archiveLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
return true;
}
case R.id.action_card_delete: {
- // TODO error handling
- syncManager.deleteCard(fullCard.getCard());
+ final WrappedLiveData<Void> deleteLiveData = syncManager.deleteCard(fullCard.getCard());
+ observeOnce(deleteLiveData, lifecycleOwner, (v) -> {
+ if (deleteLiveData.hasError()) {
+ ExceptionDialogFragment.newInstance(deleteLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
return true;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
index f800ae546..8cb13a865 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
@@ -20,6 +20,7 @@ import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.tabs.TabLayout;
import com.google.android.material.tabs.TabLayoutMediator;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityEditBinding;
import it.niedermann.nextcloud.deck.model.Account;
@@ -29,6 +30,7 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.util.CardUtil;
+import static android.graphics.Color.parseColor;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
public class EditActivity extends BrandedActivity {
@@ -72,14 +74,26 @@ public class EditActivity extends BrandedActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
-
binding = ActivityEditBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
-
setSupportActionBar(binding.toolbar);
+ viewModel = new ViewModelProvider(this).get(EditCardViewModel.class);
+ syncManager = new SyncManager(this);
+
+ loadDataFromIntent();
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ loadDataFromIntent();
+ applyBrand(parseColor(viewModel.getAccount().getColor()), parseColor(viewModel.getAccount().getTextColor()));
+ }
+
+ private void loadDataFromIntent() {
final Bundle args = getIntent().getExtras();
if (args == null || !args.containsKey(BUNDLE_KEY_ACCOUNT) || !args.containsKey(BUNDLE_KEY_BOARD_ID)) {
@@ -88,9 +102,6 @@ public class EditActivity extends BrandedActivity {
long cardId = args.getLong(BUNDLE_KEY_CARD_ID);
- viewModel = new ViewModelProvider(this).get(EditCardViewModel.class);
- syncManager = new SyncManager(this);
-
if (cardId == 0L) {
viewModel.setCreateMode(true);
if (!args.containsKey(BUNDLE_KEY_STACK_ID)) {
@@ -102,13 +113,16 @@ public class EditActivity extends BrandedActivity {
if (account == null) {
throw new IllegalArgumentException(BUNDLE_KEY_ACCOUNT + " must not be null.");
}
+ viewModel.setAccount(account);
+
final long boardId = args.getLong(BUNDLE_KEY_BOARD_ID);
observeOnce(syncManager.getFullBoardById(account.getId(), boardId), EditActivity.this, (fullBoard -> {
viewModel.setCanEdit(fullBoard.getBoard().isPermissionEdit());
invalidateOptionsMenu();
if (viewModel.isCreateMode()) {
- viewModel.initializeNewCard(account, boardId, args.getLong(BUNDLE_KEY_STACK_ID), account.getServerDeckVersionAsObject().isSupported(this));
+ viewModel.initializeNewCard(boardId, args.getLong(BUNDLE_KEY_STACK_ID), account.getServerDeckVersionAsObject().isSupported(this));
+ invalidateOptionsMenu();
String title = args.getString(BUNDLE_KEY_TITLE);
if (!TextUtils.isEmpty(title)) {
if (title.length() > viewModel.getAccount().getServerDeckVersionAsObject().getCardTitleMaxLength()) {
@@ -121,9 +135,18 @@ public class EditActivity extends BrandedActivity {
setupTitle();
} else {
observeOnce(syncManager.getCardByLocalId(account.getId(), cardId), EditActivity.this, (fullCard) -> {
- viewModel.initializeExistingCard(account, boardId, fullCard, account.getServerDeckVersionAsObject().isSupported(this));
- setupViewPager();
- setupTitle();
+ if (fullCard == null) {
+ new BrandedAlertDialogBuilder(this)
+ .setTitle(R.string.card_not_found)
+ .setMessage(R.string.card_not_found_message)
+ .setPositiveButton(R.string.simple_close, (a, b) -> super.finish())
+ .show();
+ } else {
+ viewModel.initializeExistingCard(boardId, fullCard, account.getServerDeckVersionAsObject().isSupported(this));
+ invalidateOptionsMenu();
+ setupViewPager();
+ setupTitle();
+ }
});
}
}));
@@ -264,8 +287,12 @@ public class EditActivity extends BrandedActivity {
protected void onStop() {
// Clean up zombie fragments in case of system initiated process death.
// See linked issues in https://github.com/stefan-niedermann/nextcloud-deck/issues/478
- for (Fragment fragment : getSupportFragmentManager().getFragments()) {
- getSupportFragmentManager().beginTransaction().remove(fragment).commit();
+ try {
+ for (Fragment fragment : getSupportFragmentManager().getFragments()) {
+ getSupportFragmentManager().beginTransaction().remove(fragment).commit();
+ }
+ } catch (IllegalStateException e) {
+ DeckLog.warn("onSAveInstanceState has already been called.");
}
super.onStop();
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
index 66ec0d5d8..490c1d57d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
@@ -25,27 +25,23 @@ public class EditCardViewModel extends ViewModel {
/**
* Stores a deep copy of the given fullCard to be able to compare the state at every time in #{@link EditCardViewModel#hasChanges()}
*
- * @param account Must not be null
* @param boardId Local ID, expecting a positive long value
* @param fullCard The card that is currently edited
*/
- public void initializeExistingCard(@NonNull Account account, long boardId, @NonNull FullCard fullCard, boolean isSupportedVersion) {
- this.account = account;
+ public void initializeExistingCard(long boardId, @NonNull FullCard fullCard, boolean isSupportedVersion) {
this.boardId = boardId;
this.fullCard = fullCard;
this.originalCard = new FullCard(this.fullCard);
this.isSupportedVersion = isSupportedVersion;
- hasCommentsAbility = account.getServerDeckVersionAsObject().supportsComments();
}
/**
* Stores a deep copy of the given fullCard to be able to compare the state at every time in #{@link EditCardViewModel#hasChanges()}
*
- * @param account Must not be null
* @param boardId Local ID, expecting a positive long value
* @param stackId Local ID, expecting a positive long value where the card should be created
*/
- public void initializeNewCard(@NonNull Account account, long boardId, long stackId, boolean isSupportedVersion) {
+ public void initializeNewCard(long boardId, long stackId, boolean isSupportedVersion) {
final FullCard fullCard = new FullCard();
fullCard.setLabels(new ArrayList<>());
fullCard.setAssignedUsers(new ArrayList<>());
@@ -53,7 +49,12 @@ public class EditCardViewModel extends ViewModel {
final Card card = new Card();
card.setStackId(stackId);
fullCard.setCard(card);
- initializeExistingCard(account, boardId, fullCard, isSupportedVersion);
+ initializeExistingCard(boardId, fullCard, isSupportedVersion);
+ }
+
+ public void setAccount(@NonNull Account account) {
+ this.account = account;
+ hasCommentsAbility = account.getServerDeckVersionAsObject().supportsComments();
}
public boolean hasChanges() {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
index 65a0d726d..b2e9cb012 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
@@ -5,7 +5,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.ArrayAdapter;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -16,7 +15,6 @@ import java.util.List;
import it.niedermann.nextcloud.deck.Application;
import it.niedermann.nextcloud.deck.DeckLog;
-import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityPrepareCreateBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Board;
@@ -25,6 +23,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.ImportAccountActivity;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
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 static android.graphics.Color.parseColor;
@@ -189,9 +188,7 @@ public class PrepareCreateActivity extends BrandedActivity {
finish();
} else {
- // TODO Use snackbar for better error handling
- DeckLog.error("Selected account at position " + binding.accountSelect.getSelectedItemPosition() + " is null.");
- Toast.makeText(this, R.string.error, Toast.LENGTH_LONG).show();
+ ExceptionDialogFragment.newInstance(new IllegalStateException("Selected account at position " + binding.accountSelect.getSelectedItemPosition() + " is null."), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
index def22c3f7..7b43d130c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
@@ -66,7 +66,7 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
syncManager = new SyncManager(activity);
- adapter = new CardAdapter(requireContext(), mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), stackId, mainViewModel.currentBoardHasEditPermission(), syncManager, this, (requireActivity() instanceof SelectCardListener) ? (SelectCardListener) requireActivity() : null);
+ adapter = new CardAdapter(requireContext(), getChildFragmentManager(), mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), stackId, mainViewModel.currentBoardHasEditPermission(), syncManager, this, (requireActivity() instanceof SelectCardListener) ? (SelectCardListener) requireActivity() : null);
binding.recyclerView.setAdapter(adapter);
if (onScrollListener != null) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java
new file mode 100644
index 000000000..e87e9c955
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java
@@ -0,0 +1,67 @@
+package it.niedermann.nextcloud.deck.ui.widget.singlecard;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.ui.MainActivity;
+import it.niedermann.nextcloud.deck.ui.card.SelectCardListener;
+
+public class SelectCardForWidgetActivity extends MainActivity implements SelectCardListener {
+
+ private int appWidgetId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final Intent intent = getIntent();
+ if (intent == null) {
+ finish();
+ return;
+ }
+ final Bundle args = intent.getExtras();
+ if (args == null) {
+ finish();
+ return;
+ }
+ appWidgetId = args.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ finish();
+ }
+ }
+
+ @Override
+ public void onCardSelected(FullCard fullCard) {
+ syncManager.addOrUpdateSingleCardWidget(appWidgetId, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId());
+ final Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null,
+ getApplicationContext(), SingleCardWidget.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ setResult(RESULT_OK, updateIntent);
+ getApplicationContext().sendBroadcast(updateIntent);
+ finish();
+ }
+
+ @Override
+ protected void setCurrentBoard(@NonNull Board board) {
+ super.setCurrentBoard(board);
+ binding.addStackButton.setVisibility(View.GONE);
+ binding.fab.setVisibility(View.GONE);
+ binding.toolbar.setTitle(R.string.simple_select);
+ }
+
+ @Override
+ protected void showFabIfEditPermissionGranted() { /* Silence is gold */ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return true;
+ }
+
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java
new file mode 100644
index 000000000..783f98e00
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java
@@ -0,0 +1,153 @@
+package it.niedermann.nextcloud.deck.ui.widget.singlecard;
+
+import android.app.PendingIntent;
+import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetProvider;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.view.View;
+import android.widget.RemoteViews;
+
+import androidx.annotation.DrawableRes;
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+
+import java.util.NoSuchElementException;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+import it.niedermann.nextcloud.deck.util.DateUtil;
+
+public class SingleCardWidget extends AppWidgetProvider {
+
+ void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
+ final SyncManager syncManager = new SyncManager(context);
+
+ for (int appWidgetId : appWidgetIds) {
+ new Thread(() -> {
+ try {
+ final FullSingleCardWidgetModel fullModel = syncManager.getSingleCardWidgetModelDirectly(appWidgetId);
+
+ final Intent intent = EditActivity.createEditCardIntent(context, fullModel.getAccount(), fullModel.getModel().getBoardId(), fullModel.getFullCard().getLocalId());
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_single_card);
+
+ views.setOnClickPendingIntent(R.id.widget_card, pendingIntent);
+
+ views.setTextViewText(R.id.title, fullModel.getFullCard().getCard().getTitle());
+ views.setTextViewText(R.id.description, fullModel.getFullCard().getCard().getDescription());
+
+ if (fullModel.getFullCard().getCard().getDueDate() != null) {
+ views.setTextViewText(R.id.card_due_date, DateUtil.getRelativeDateTimeString(context, fullModel.getFullCard().getCard().getDueDate().getTime()));
+ // TODO Use multiple views for background colors and only set the necessary to View.VISIBLE
+ // https://stackoverflow.com/a/3376537
+ // Because otherwise using Reflection is the only way
+ views.setViewVisibility(R.id.card_due_date, View.VISIBLE);
+ views.setViewVisibility(R.id.card_due_date_image, View.VISIBLE);
+ setImageDrawable(views, R.id.card_due_date_image, R.drawable.calendar_blank_grey600_24dp);
+ } else {
+ views.setViewVisibility(R.id.card_due_date, View.GONE);
+ views.setViewVisibility(R.id.card_due_date_image, View.GONE);
+ }
+
+
+ final String counterMaxValue = context.getString(R.string.counter_max_value);
+
+ final int attachmentsCount = fullModel.getFullCard().getAttachments().size();
+ if (attachmentsCount == 0) {
+ views.setViewVisibility(R.id.card_count_attachments, View.GONE);
+ views.setViewVisibility(R.id.card_count_attachments_image, View.GONE);
+ } else {
+ views.setViewVisibility(R.id.card_count_attachments, View.VISIBLE);
+ views.setViewVisibility(R.id.card_count_attachments_image, View.VISIBLE);
+ setupCounter(views, R.id.card_count_attachments, attachmentsCount, counterMaxValue);
+ setImageDrawable(views, R.id.card_count_attachments_image, R.drawable.ic_check_grey600_24dp);
+ }
+
+ final int commentsCount = fullModel.getFullCard().getCommentCount();
+ if (commentsCount == 0) {
+ views.setViewVisibility(R.id.card_count_comments, View.GONE);
+ views.setViewVisibility(R.id.card_count_comments_image, View.GONE);
+ } else {
+ views.setViewVisibility(R.id.card_count_comments, View.VISIBLE);
+ views.setViewVisibility(R.id.card_count_comments_image, View.VISIBLE);
+ setupCounter(views, R.id.card_count_comments, commentsCount, counterMaxValue);
+ setImageDrawable(views, R.id.card_count_comments_image, R.drawable.ic_comment_white_24dp);
+ }
+
+ final Card.TaskStatus taskStatus = fullModel.getFullCard().getCard().getTaskStatus();
+ if (taskStatus.taskCount > 0) {
+ views.setViewVisibility(R.id.card_count_tasks, View.VISIBLE);
+ views.setViewVisibility(R.id.card_count_tasks_image, View.VISIBLE);
+ views.setTextViewText(R.id.card_count_tasks, context.getResources().getString(R.string.task_count, String.valueOf(taskStatus.doneCount), String.valueOf(taskStatus.taskCount)));
+ setImageDrawable(views, R.id.card_count_tasks_image, R.drawable.ic_attach_file_grey600_24dp);
+ } else {
+ views.setViewVisibility(R.id.card_count_tasks, View.GONE);
+ views.setViewVisibility(R.id.card_count_tasks_image, View.GONE);
+ }
+
+ awm.updateAppWidget(appWidgetId, views);
+ } catch (NoSuchElementException e) {
+ // onUpdate has been triggered before the user finished configuring the widget
+ }
+ }).start();
+ }
+ }
+
+ private static void setupCounter(@NonNull RemoteViews views, @IdRes int textViewId, int count, String counterMaxValue) {
+ if (count > 99) {
+ views.setTextViewText(textViewId, counterMaxValue);
+ } else if (count > 1 || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ views.setTextViewText(textViewId, String.valueOf(count));
+ } else if (count == 1) {
+ views.setTextViewText(textViewId, "");
+ }
+ }
+
+ private static void setImageDrawable(@NonNull RemoteViews views, @IdRes int imageView, @DrawableRes int image) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ views.setImageViewResource(imageView, image);
+ }
+ }
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+ updateAppWidget(context, appWidgetManager, appWidgetIds);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+ AppWidgetManager awm = AppWidgetManager.getInstance(context);
+
+ updateAppWidget(context, AppWidgetManager.getInstance(context),
+ (awm.getAppWidgetIds(new ComponentName(context, SingleCardWidget.class))));
+ }
+
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ final SyncManager syncManager = new SyncManager(context);
+
+ for (int appWidgetId : appWidgetIds) {
+ syncManager.deleteSingleCardWidgetModel(appWidgetId);
+ }
+
+ super.onDeleted(context, appWidgetIds);
+ }
+
+
+ /**
+ * Updates UI data of all {@link SingleCardWidget} instances
+ */
+ public static void notifyDatasetChanged(Context context) {
+ Intent intent = new Intent(context, SingleCardWidget.class);
+ intent.setAction("android.appwidget.action.APPWIDGET_UPDATE");
+ context.sendBroadcast(intent);
+ }
+}