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-07-13 14:43:31 +0300
committerStefan Niedermann <info@niedermann.it>2020-07-13 14:43:31 +0300
commite8f941b8e0f956d57e608c998345436eb6a76275 (patch)
tree15aa1d1e1d42cfb98181f282d156dd17aaa34abd
parent9d0c0666a216893a2d213b284113968067151769 (diff)
parent85e3c11d33865c73497b07c69bf34d0ad7ecf84f (diff)
Merge branch '453-move-cards'
# Conflicts: # app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java56
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java49
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java124
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java195
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java22
-rw-r--r--app/src/main/res/layout/dialog_move_card.xml63
-rw-r--r--app/src/main/res/layout/fragment_pick_stack.xml28
-rw-r--r--app/src/main/res/values/strings.xml1
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java3
14 files changed, 507 insertions, 59 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
index b9f6f403d..7c1727fb7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
@@ -40,6 +40,7 @@ public class GsonConfig {
Type capabilities = new TypeToken<Capabilities>() {}.getType();
Type ocsUserList = new TypeToken<OcsUserList>() {}.getType();
Type activity = new TypeToken<Activity>() {}.getType();
+ Type activityList = new TypeToken<List<Activity>>() {}.getType();
Type attachment = new TypeToken<Attachment>() {}.getType();
Type attachmentList = new TypeToken<List<Attachment>>() {}.getType();
Type comment = new TypeToken<OcsComment>() {}.getType();
@@ -59,6 +60,7 @@ public class GsonConfig {
.registerTypeAdapter(capabilities, new NextcloudDeserializer<>("capability", Capabilities.class))
.registerTypeAdapter(ocsUserList, new NextcloudDeserializer<>("ocsUserList", OcsUserList.class))
.registerTypeAdapter(activity, new NextcloudDeserializer<>("activity", Activity.class))
+ .registerTypeAdapter(activityList, new NextcloudDeserializer<>("activityList", Activity.class))
.registerTypeAdapter(attachmentList, new NextcloudArrayDeserializer<>("attachments", Attachment.class))
.registerTypeAdapter(attachment, new NextcloudDeserializer<>("attachment", Attachment.class))
.registerTypeAdapter(comment, new NextcloudDeserializer<>("comment", OcsComment.class))
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 f8d7424a7..097f555b3 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
@@ -887,7 +887,7 @@ public class SyncManager {
doAsync(() -> {
FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId());
if (fullCard == null) {
- throw new IllegalArgumentException("card to delete does not exist.");
+ throw new IllegalArgumentException("card with id " + card.getLocalId() + " to delete does not exist.");
}
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
@@ -1074,7 +1074,7 @@ public class SyncManager {
}
// ### get rid of original card where it is now.
Card originalInnerCard = originalCard.getCard();
- deleteCard(originalInnerCard);
+ deleteCard(new Card(originalInnerCard));
// ### clone card itself
Card targetCard = originalInnerCard;
targetCard.setAccountId(targetAccountId);
@@ -1083,7 +1083,9 @@ public class SyncManager {
targetCard.setStatusEnum(DBStatus.LOCAL_EDITED);
targetCard.setStackId(targetStackLocalId);
targetCard.setOrder(newIndex);
- //TODO: this needs to propagate to server as well, since anything else propagates as well (otherwise card isn't known on server)
+ targetCard.setArchived(false);
+ targetCard.setAttachmentCount(0);
+ targetCard.setCommentsUnread(0);
FullCard fullCardForServerPropagation = new FullCard();
fullCardForServerPropagation.setCard(targetCard);
@@ -1092,7 +1094,11 @@ public class SyncManager {
FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId);
User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName());
CountDownLatch latch = new CountDownLatch(1);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
+ ServerAdapter serverToUse = serverAdapter;
+ if (originAccountId != targetAccountId) {
+ serverToUse = new ServerAdapter(appContext, targetAccount.getName());
+ }
+ new DataPropagationHelper(serverToUse, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
@Override
public void onResponse(FullCard response) {
targetCard.setId(response.getId());
@@ -1106,8 +1112,10 @@ public class SyncManager {
throw new RuntimeException("unable to create card in moveCard target", throwable);
}
}, (FullCard entity, FullCard response) -> {
- response.getCard().setUserId(entity.getCard().getUserId());
+ response.getCard().setUserId(userOfTargetAccount.getLocalId());
response.getCard().setStackId(targetFullStack.getLocalId());
+ entity.getCard().setUserId(userOfTargetAccount.getLocalId());
+ entity.getCard().setStackId(targetFullStack.getLocalId());
});
try {
@@ -1150,10 +1158,10 @@ public class SyncManager {
originalLabel.setLocalId(null);
originalLabel.setStatusEnum(DBStatus.LOCAL_EDITED);
originalLabel.setAccountId(targetBoard.getAccountId());
- createAndAssignLabelToCard(originalBoard.getAccountId(), originalLabel, newCardId);
+ createAndAssignLabelToCard(targetBoard.getAccountId(), originalLabel, newCardId, serverToUse);
}
} else {
- assignLabelToCard(existingMatch, targetCard);
+ assignLabelToCard(existingMatch, targetCard, serverToUse);
}
}
@@ -1212,14 +1220,18 @@ public class SyncManager {
return liveData;
}
- @AnyThread
public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId) {
+ return createAndAssignLabelToCard(accountId, label, localCardId, serverAdapter);
+ }
+
+ @AnyThread
+ private MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId, ServerAdapter serverAdapterToUse) {
MutableLiveData<Label> liveData = new MutableLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
Board board = dataBaseAdapter.getBoardByLocalCardIdDirectly(localCardId);
label.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
+ new DataPropagationHelper(serverAdapterToUse, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
@Override
public void onResponse(Label response) {
assignLabelToCard(response, dataBaseAdapter.getCardByLocalIdDirectly(accountId, localCardId));
@@ -1289,6 +1301,11 @@ public class SyncManager {
@AnyThread
public void assignLabelToCard(@NonNull Label label, @NonNull Card card) {
+ assignLabelToCard(label, card, serverAdapter);
+ }
+
+ @AnyThread
+ public void assignLabelToCard(@NonNull Label label, @NonNull Card card, ServerAdapter serverAdapterToUse) {
doAsync(() -> {
final long localLabelId = label.getLocalId();
final long localCardId = card.getLocalId();
@@ -1299,8 +1316,8 @@ public class SyncManager {
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
- if (serverAdapter.hasInternetConnection()) {
- serverAdapter.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
+ if (serverAdapterToUse.hasInternetConnection()) {
+ serverAdapterToUse.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
@@ -1636,15 +1653,14 @@ public class SyncManager {
@AnyThread
private static Attachment populateAttachmentEntityForFile(@NonNull Attachment target, long localCardId, @NonNull String mimeType, @NonNull File file) {
- Attachment attachment = target;
- attachment.setCardId(localCardId);
- attachment.setMimetype(mimeType);
- attachment.setData(file.getName());
- attachment.setFilename(file.getName());
- attachment.setBasename(file.getName());
- attachment.setLocalPath(file.getAbsolutePath());
- attachment.setFilesize(file.length());
- return attachment;
+ target.setCardId(localCardId);
+ target.setMimetype(mimeType);
+ target.setData(file.getName());
+ target.setFilename(file.getName());
+ target.setBasename(file.getName());
+ target.setLocalPath(file.getAbsolutePath());
+ target.setFilesize(file.length());
+ return target;
}
@AnyThread
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 bd189e846..2d241019f 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
@@ -52,7 +52,7 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
Board getBoardByLocalCardIdDirectly(long localCardId);
- @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
+ @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId and c.stackId = s.localId")
FullBoard getFullBoardByLocalCardIdDirectly(long localCardId);
@Transaction
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 2e1f4bece..27a6518ee 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
@@ -8,6 +8,7 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -17,7 +18,6 @@ import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
import it.niedermann.android.crosstabdnd.DragAndDropAdapter;
@@ -28,15 +28,14 @@ import it.niedermann.nextcloud.deck.databinding.ItemCardCompactBinding;
import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultBinding;
import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultOnlyTitleBinding;
import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.Stack;
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.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
+import it.niedermann.nextcloud.deck.ui.movecard.MoveCardDialogFragment;
import static androidx.preference.PreferenceManager.getDefaultSharedPreferences;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
@@ -46,9 +45,11 @@ import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> implements DragAndDropAdapter<FullCard>, CardOptionsItemSelectedListener, Branded {
private final boolean compactMode;
+ @NonNull
protected final SyncManager syncManager;
-
+ @NonNull
protected final FragmentManager fragmentManager;
+ @NonNull
protected final Account account;
@Nullable
protected final Long boardRemoteId;
@@ -59,12 +60,13 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
private final Context context;
@Nullable
private final SelectCardListener selectCardListener;
- protected List<FullCard> cardList = new LinkedList<>();
+ @NonNull
+ protected List<FullCard> cardList = new ArrayList<>();
+ @NonNull
protected LifecycleOwner lifecycleOwner;
@NonNull
- final private List<FullStack> availableStacks = new ArrayList<>();
protected String counterMaxValue;
-
+ @ColorInt
protected int mainColor;
@StringRes
private int shareLinkRes;
@@ -84,10 +86,6 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
this.selectCardListener = selectCardListener;
this.mainColor = ContextCompat.getColor(context, R.color.defaultBrand);
this.compactMode = getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.pref_key_compact), false);
- syncManager.getStacksForBoard(account.getId(), boardLocalId).observe(this.lifecycleOwner, (stacks) -> {
- availableStacks.clear();
- availableStacks.addAll(stacks);
- });
setHasStableIds(true);
}
@@ -155,7 +153,7 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
@Override
public int getItemCount() {
- return cardList == null ? 0 : cardList.size();
+ return cardList.size();
}
public void insertItem(FullCard fullCard, int position) {
@@ -163,6 +161,7 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
notifyItemInserted(position);
}
+ @NonNull
@Override
public List<FullCard> getItemList() {
return this.cardList;
@@ -213,28 +212,8 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
return true;
}
case R.id.action_card_move: {
- int currentStackItem = 0;
- CharSequence[] items = new CharSequence[availableStacks.size()];
- for (int i = 0; i < availableStacks.size(); i++) {
- final Stack stack = availableStacks.get(i).getStack();
- items[i] = stack.getTitle();
- if (stack.getLocalId().equals(stackId)) {
- currentStackItem = i;
- }
- }
- final FullCard newCard = fullCard;
- new BrandedAlertDialogBuilder(context)
- .setSingleChoiceItems(items, currentStackItem, (dialog, which) -> {
- dialog.cancel();
- newCard.getCard().setStackId(availableStacks.get(which).getStack().getLocalId());
- LiveDataHelper.observeOnce(syncManager.updateCard(newCard), lifecycleOwner, (c) -> {
- // Nothing to do here...
- });
- DeckLog.log("Moved card \"" + fullCard.getCard().getTitle() + "\" to \"" + availableStacks.get(which).getStack().getTitle() + "\"");
- })
- .setNeutralButton(android.R.string.cancel, null)
- .setTitle(context.getString(R.string.action_card_move_title, fullCard.getCard().getTitle()))
- .show();
+ DeckLog.verbose("[Move card] Launch move dialog for " + Card.class.getSimpleName() + " \"" + fullCard.getCard().getTitle() + "\" (#" + fullCard.getLocalId() + ") from " + Stack.class.getSimpleName() + " #" + +stackId);
+ MoveCardDialogFragment.newInstance(fullCard.getAccountId(), boardLocalId, fullCard.getLocalId()).show(fragmentManager, MoveCardDialogFragment.class.getSimpleName());
return true;
}
case R.id.action_card_archive: {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
index c236fa4c5..2d3ece255 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
@@ -3,5 +3,5 @@ package it.niedermann.nextcloud.deck.ui.card.attachments;
import it.niedermann.nextcloud.deck.model.Attachment;
public interface AttachmentDeletedListener {
- void onAttachmentDeleted(Attachment attachment);
- } \ No newline at end of file
+ void onAttachmentDeleted(Attachment attachment);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
new file mode 100644
index 000000000..97a011398
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
@@ -0,0 +1,124 @@
+package it.niedermann.nextcloud.deck.ui.movecard;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.DialogMoveCardBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.model.full.FullStack;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
+import it.niedermann.nextcloud.deck.ui.branding.BrandingUtil;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackFragment;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackListener;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+public class MoveCardDialogFragment extends BrandedDialogFragment implements PickStackListener {
+
+ private static final String KEY_ORIGIN_ACCOUNT_ID = "account_id";
+ private static final String KEY_ORIGIN_BOARD_LOCAL_ID = "board_local_id";
+ private static final String KEY_ORIGIN_CARD_LOCAL_ID = "card_local_id";
+ private Long originAccountId;
+ private Long originBoardLocalId;
+ private Long originCardLocalId;
+
+ private DialogMoveCardBinding binding;
+ private PickStackFragment fragment;
+ private MoveCardListener moveCardListener;
+
+ private Account selectedAccount;
+ private Board selectedBoard;
+ private FullStack selectedStack;
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof MoveCardListener) {
+ this.moveCardListener = (MoveCardListener) getParentFragment();
+ } else if (context instanceof MoveCardListener) {
+ this.moveCardListener = (MoveCardListener) context;
+ } else {
+ throw new IllegalArgumentException("Caller must implement " + MoveCardListener.class.getSimpleName());
+ }
+
+ final Bundle args = requireArguments();
+ originAccountId = args.getLong(KEY_ORIGIN_ACCOUNT_ID, -1L);
+ if (originAccountId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_ACCOUNT_ID);
+ }
+ originCardLocalId = args.getLong(KEY_ORIGIN_CARD_LOCAL_ID, -1L);
+ if (originCardLocalId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_CARD_LOCAL_ID);
+ }
+ originBoardLocalId = args.getLong(KEY_ORIGIN_BOARD_LOCAL_ID, -1L);
+ if (originBoardLocalId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_BOARD_LOCAL_ID);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ binding = DialogMoveCardBinding.inflate(inflater);
+ binding.submit.setOnClickListener((v) -> {
+ DeckLog.verbose("[Move card] Attempt to move to " + Stack.class.getSimpleName() + " #" + selectedStack.getLocalId());
+ this.moveCardListener.move(originAccountId, originCardLocalId, selectedAccount.getId(), selectedBoard.getLocalId(), selectedStack.getLocalId());
+ dismiss();
+ });
+ binding.cancel.setOnClickListener((v) -> dismiss());
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ fragment = new PickStackFragment();
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, fragment)
+ .commit();
+ }
+
+ @Override
+ public void onStackPicked(@NonNull Account account, @Nullable Board board, @Nullable FullStack fullStack) {
+ this.selectedAccount = account;
+ this.selectedBoard = board;
+ this.selectedStack = fullStack;
+ if (board == null || fullStack == null) {
+ binding.submit.setEnabled(false);
+ binding.moveWarning.setVisibility(GONE);
+ } else {
+ binding.submit.setEnabled(true);
+ binding.moveWarning.setVisibility(board.getLocalId().equals(originBoardLocalId) ? GONE : VISIBLE);
+ }
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+ final ColorStateList mainColorStateList = ColorStateList.valueOf(BrandingUtil.getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor));
+ binding.cancel.setTextColor(mainColorStateList);
+ binding.submit.setTextColor(mainColorStateList);
+ }
+
+ public static DialogFragment newInstance(long originAccountId, long originBoardLocalId, Long originCardLocalId) {
+ final DialogFragment dialogFragment = new MoveCardDialogFragment();
+ final Bundle args = new Bundle();
+ args.putLong(KEY_ORIGIN_ACCOUNT_ID, originAccountId);
+ args.putLong(KEY_ORIGIN_BOARD_LOCAL_ID, originBoardLocalId);
+ args.putLong(KEY_ORIGIN_CARD_LOCAL_ID, originCardLocalId);
+ dialogFragment.setArguments(args);
+ return dialogFragment;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java
new file mode 100644
index 000000000..f6f7a7a1f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java
@@ -0,0 +1,5 @@
+package it.niedermann.nextcloud.deck.ui.movecard;
+
+public interface MoveCardListener {
+ void move(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
new file mode 100644
index 000000000..6ebbed99b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
@@ -0,0 +1,195 @@
+package it.niedermann.nextcloud.deck.ui.pickstack;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.databinding.FragmentPickStackBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.full.FullStack;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.ImportAccountActivity;
+import it.niedermann.nextcloud.deck.ui.preparecreate.AccountAdapter;
+import it.niedermann.nextcloud.deck.ui.preparecreate.BoardAdapter;
+import it.niedermann.nextcloud.deck.ui.preparecreate.SelectedListener;
+import it.niedermann.nextcloud.deck.ui.preparecreate.StackAdapter;
+
+import static androidx.lifecycle.Transformations.switchMap;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentBoardId;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentStackId;
+
+public class PickStackFragment extends Fragment {
+
+ private FragmentPickStackBinding binding;
+
+ private SyncManager syncManager;
+
+ private PickStackListener pickStackListener;
+
+ private long lastAccountId;
+ private long lastBoardId;
+ private long lastStackId;
+
+ private ArrayAdapter<Account> accountAdapter;
+ private ArrayAdapter<Board> boardAdapter;
+ private ArrayAdapter<FullStack> stackAdapter;
+
+ @Nullable
+ private LiveData<List<Board>> boardsLiveData;
+ @NonNull
+ private Observer<List<Board>> boardsObserver = (boards) -> {
+ boardAdapter.clear();
+ boardAdapter.addAll(boards);
+ binding.boardSelect.setEnabled(true);
+
+ if (boards.size() > 0) {
+ binding.boardSelect.setEnabled(true);
+
+ Board boardToSelect = null;
+ for (Board board : boards) {
+ if (board.getLocalId() == lastBoardId) {
+ boardToSelect = board;
+ break;
+ }
+ }
+ if(boardToSelect == null) {
+ boardToSelect = boards.get(0);
+ }
+ binding.boardSelect.setSelection(boardAdapter.getPosition(boardToSelect));
+ } else {
+ binding.boardSelect.setEnabled(false);
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), null, null);
+ }
+ };
+
+ @Nullable
+ private LiveData<List<FullStack>> stacksLiveData;
+ @NonNull
+ private Observer<List<FullStack>> stacksObserver = (fullStacks) -> {
+ stackAdapter.clear();
+ stackAdapter.addAll(fullStacks);
+
+ if (fullStacks.size() > 0) {
+ binding.stackSelect.setEnabled(true);
+
+ FullStack fullStackToSelect = null;
+ for (FullStack fullStack : fullStacks) {
+ if (fullStack.getLocalId() == lastStackId) {
+ fullStackToSelect = fullStack;
+ break;
+ }
+ }
+ if (fullStackToSelect == null) {
+ fullStackToSelect = fullStacks.get(0);
+ }
+ binding.stackSelect.setSelection(stackAdapter.getPosition(fullStackToSelect));
+ } else {
+ binding.stackSelect.setEnabled(false);
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), (Board) binding.boardSelect.getSelectedItem(), null);
+ }
+ };
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof PickStackListener) {
+ this.pickStackListener = (PickStackListener) getParentFragment();
+ } else if (context instanceof PickStackListener) {
+ this.pickStackListener = (PickStackListener) context;
+ } else {
+ throw new IllegalArgumentException("Caller must implement " + PickStackListener.class.getSimpleName());
+ }
+ DeckLog.error("PICKSTACK: onAttach successful");
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = FragmentPickStackBinding.inflate(getLayoutInflater());
+
+ accountAdapter = new AccountAdapter(requireContext());
+ binding.accountSelect.setAdapter(accountAdapter);
+ binding.accountSelect.setEnabled(false);
+ boardAdapter = new BoardAdapter(requireContext());
+ binding.boardSelect.setAdapter(boardAdapter);
+ binding.stackSelect.setEnabled(false);
+ stackAdapter = new StackAdapter(requireContext());
+ binding.stackSelect.setAdapter(stackAdapter);
+ binding.stackSelect.setEnabled(false);
+
+ syncManager = new SyncManager(requireContext());
+
+ switchMap(syncManager.hasAccounts(), hasAccounts -> {
+ if (hasAccounts) {
+ return syncManager.readAccounts();
+ } else {
+ startActivityForResult(new Intent(requireActivity(), ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ return null;
+ }
+ }).observe(getViewLifecycleOwner(), (List<Account> accounts) -> {
+ if (accounts == null || accounts.size() == 0) {
+ throw new IllegalStateException("hasAccounts() returns true, but readAccounts() returns null or has no entry");
+ }
+
+ lastAccountId = readCurrentAccountId(requireContext());
+ lastBoardId = readCurrentBoardId(requireContext(), lastAccountId);
+ lastStackId = readCurrentStackId(requireContext(), lastAccountId, lastBoardId);
+
+ accountAdapter.clear();
+ accountAdapter.addAll(accounts);
+ binding.accountSelect.setEnabled(true);
+
+ for (Account account : accounts) {
+ if (account.getId() == lastAccountId) {
+ binding.accountSelect.setSelection(accountAdapter.getPosition(account));
+ break;
+ }
+ }
+ });
+
+ binding.accountSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ updateLiveDataSource(boardsLiveData, boardsObserver, syncManager.getBoardsWithEditPermission(parent.getSelectedItemId()));
+ });
+
+ binding.boardSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ updateLiveDataSource(stacksLiveData, stacksObserver, syncManager.getStacksForBoard(binding.accountSelect.getSelectedItemId(), parent.getSelectedItemId()));
+ });
+
+ binding.stackSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), (Board) binding.boardSelect.getSelectedItem(), (FullStack) parent.getSelectedItem());
+ });
+
+ return binding.getRoot();
+ }
+
+ /**
+ * Updates the source of the given liveData and de- and reregisters the given observer.
+ */
+ private <T> void updateLiveDataSource(@Nullable LiveData<T> liveData, Observer<T> observer, LiveData<T> newSource) {
+ if (liveData != null) {
+ liveData.removeObserver(observer);
+ }
+ liveData = newSource;
+ liveData.observe(getViewLifecycleOwner(), observer);
+ }
+
+ public static PickStackFragment newInstance() {
+ return new PickStackFragment();
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java
new file mode 100644
index 000000000..0abb4dac9
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.ui.pickstack;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.full.FullStack;
+
+public interface PickStackListener {
+ void onStackPicked(@NonNull Account account, @Nullable Board board, @Nullable FullStack fullStack);
+}
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 726a74184..72c5ffc84 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
@@ -20,15 +20,22 @@ import java.util.List;
import it.niedermann.android.crosstabdnd.DragAndDropTab;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.databinding.FragmentStackBinding;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.card.CardAdapter;
import it.niedermann.nextcloud.deck.ui.card.SelectCardListener;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.filter.FilterViewModel;
+import it.niedermann.nextcloud.deck.ui.movecard.MoveCardListener;
-public class StackFragment extends BrandedFragment implements DragAndDropTab<CardAdapter> {
+import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
+
+public class StackFragment extends BrandedFragment implements DragAndDropTab<CardAdapter>, MoveCardListener {
private static final String KEY_STACK_ID = "stackId";
@@ -153,4 +160,17 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
return fragment;
}
+
+ @Override
+ public void move(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
+ WrappedLiveData<Void> liveData = syncManager.moveCard(originAccountId, originCardLocalId, targetAccountId, targetBoardLocalId, targetStackLocalId);
+ observeOnce(liveData, requireActivity(), (next) -> {
+ if (liveData.hasError()) {
+ ExceptionDialogFragment.newInstance(liveData.getError(), null).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ } else {
+ DeckLog.log("Moved " + Card.class.getSimpleName() + " \"" + originCardLocalId + "\" to " + Stack.class.getSimpleName() + " \"" + targetStackLocalId + "\"");
+ }
+ });
+ }
+
} \ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_move_card.xml b/app/src/main/res/layout/dialog_move_card.xml
new file mode 100644
index 000000000..e94f63b74
--- /dev/null
+++ b/app/src/main/res/layout/dialog_move_card.xml
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="@dimen/spacer_1x">
+
+ <ScrollView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <FrameLayout
+ android:id="@+id/fragment_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/move_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:drawableStart="@drawable/ic_warning_white_24dp"
+ android:drawablePadding="@dimen/spacer_3x"
+ android:paddingStart="@dimen/spacer_3x"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:text="@string/move_warning"
+ android:textColor="@color/danger"
+ android:visibility="gone"
+ app:drawableTint="@color/danger"
+ tools:visibility="visible" />
+
+ <com.google.android.flexbox.FlexboxLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacer_1x"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_1x"
+ app:justifyContent="space_between">
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/Widget.MaterialComponents.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:layout_marginEnd="@dimen/spacer_1x"
+ android:text="@android:string/cancel"
+ android:textColor="@color/defaultBrand" />
+
+ <Button
+ android:id="@+id/submit"
+ style="@style/Widget.MaterialComponents.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:text="@string/action_card_move"
+ android:textColor="@color/defaultBrand" />
+ </com.google.android.flexbox.FlexboxLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_pick_stack.xml b/app/src/main/res/layout/fragment_pick_stack.xml
new file mode 100644
index 000000000..9769969ff
--- /dev/null
+++ b/app/src/main/res/layout/fragment_pick_stack.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/account_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_account"
+ tools:listitem="@layout/item_prepare_create_account" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/board_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_board"
+ tools:listitem="@layout/item_board" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/stack_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_list"
+ tools:listitem="@layout/item_board" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a43c29e1e..c345bd211 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -282,4 +282,5 @@
</plurals>
<string name="simple_report">Report</string>
<string name="error_action_open_battery_settings">Battery settings</string>
+ <string name="move_warning">Neither comments nor attachments can be transferred when moving the card to another board.</string>
</resources>
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
index 4484d04f0..a143ccaee 100644
--- a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
@@ -1,5 +1,7 @@
package it.niedermann.android.crosstabdnd;
+import androidx.annotation.NonNull;
+
import java.util.List;
public interface DragAndDropAdapter<Model> {
@@ -10,5 +12,6 @@ public interface DragAndDropAdapter<Model> {
void insertItem(Model item, int position);
+ @NonNull
List<Model> getItemList();
}