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

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2023-03-24 13:28:50 +0300
committerStefan Niedermann <info@niedermann.it>2023-03-24 13:28:50 +0300
commit4f31f75f6098ad2c1e3f418e8d7f7d8902a0d273 (patch)
treef9cd0d0397dd00a412fcdf76884bc83be8a6c036
parent41ae988679aa6f84dfb56479eabd9e1c1209405c (diff)
feat: Implement new SearchBar
Signed-off-by: Stefan Niedermann <info@niedermann.it>
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/database/DataBaseAdapter.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/database/dao/CardDao.java49
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/repository/BaseRepository.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java1
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainActivity.java139
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainViewModel.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchAdapter.java140
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchCardViewHolder.java74
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchResults.java39
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchStackViewHolder.java28
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchViewHolder.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/theme/DeckViewThemeUtils.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/FilterIndicator.java27
-rw-r--r--app/src/main/res/drawable/filter.xml14
-rw-r--r--app/src/main/res/drawable/filter_active.xml13
-rw-r--r--app/src/main/res/drawable/ic_filter_list_white_24dp.xml2
-rw-r--r--app/src/main/res/layout/activity_main.xml293
-rw-r--r--app/src/main/res/layout/item_search_card.xml52
-rw-r--r--app/src/main/res/layout/item_search_stack.xml14
-rw-r--r--app/src/main/res/menu/main_menu.xml20
-rw-r--r--app/src/main/res/values/strings.xml4
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/database/DataBaseAdapterTest.java57
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/database/DeckDatabaseTestUtil.java8
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1021009.txt1
39 files changed, 864 insertions, 289 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/database/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/database/DataBaseAdapter.java
index dc5883ba9..e0b754866 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/database/DataBaseAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/database/DataBaseAdapter.java
@@ -35,6 +35,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
@@ -114,9 +115,9 @@ public class DataBaseAdapter {
@VisibleForTesting
protected DataBaseAdapter(@NonNull Context applicationContext,
- @NonNull DeckDatabase db,
- @NonNull ExecutorService widgetNotifierExecutor,
- @NonNull ExecutorService executor) {
+ @NonNull DeckDatabase db,
+ @NonNull ExecutorService widgetNotifierExecutor,
+ @NonNull ExecutorService executor) {
this.context = applicationContext;
this.db = db;
this.widgetNotifierExecutor = widgetNotifierExecutor;
@@ -866,6 +867,44 @@ public class DataBaseAdapter {
.distinctUntilChanged();
}
+ /**
+ * Search all {@link FullCard}s grouped by {@link Stack}s which contain the term in {@link Card#getTitle()} or {@link Card#getDescription()}.
+ * {@link Stack}s are sorted by {@link Stack#getOrder()}, {@link Card}s for each {@link Stack} are sorted by {@link Card#getOrder()}.
+ */
+ public LiveData<Map<Stack, List<FullCard>>> searchCards(final long accountId, final long localBoardId, @NonNull String term, int limitPerStack) {
+ String sqlSearchTerm = term.trim();
+ if (sqlSearchTerm.isEmpty()) {
+ throw new IllegalArgumentException("empty search term");
+ }
+ sqlSearchTerm = "%" + sqlSearchTerm + "%";
+
+
+ return new ReactiveLiveData<>(db.getCardDao().searchCard(accountId, localBoardId, sqlSearchTerm))
+ .map(result -> mapToStacksForCardSearch(result, limitPerStack), executor);
+ }
+
+ private Map<Stack, List<FullCard>> mapToStacksForCardSearch(List<FullCard> matches, int limitPerStack) {
+ Map<Stack, List<FullCard>> result = new HashMap<>();
+ if (matches != null && !matches.isEmpty()) {
+ // results are sorted by stack -> jackpot:
+ Stack lastStack = null;
+ for (FullCard card : matches) {
+ // find right bucket
+ if (lastStack == null || !Objects.equals(lastStack.getLocalId(), card.getCard().getStackId())) {
+ lastStack = db.getStackDao().getStackByLocalIdDirectly(card.getCard().getStackId());
+ }
+ // check if bucket exists
+ List<FullCard> fullCards = result.computeIfAbsent(lastStack, k -> new ArrayList<>());
+ // create bucket
+ if (fullCards.size() < limitPerStack) {
+ // put into bucket
+ fullCards.add(card);
+ }
+ }
+ }
+ return result;
+ }
+
public LiveData<List<User>> findProposalsForUsersToAssign(final long accountId, long boardId, long notAssignedToLocalCardId, final int topX) {
return new ReactiveLiveData<>(db.getUserDao().findProposalsForUsersToAssign(accountId, boardId, notAssignedToLocalCardId, topX))
.distinctUntilChanged();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/database/dao/CardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/database/dao/CardDao.java
index fbbb1708b..5d25f943f 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/database/dao/CardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/database/dao/CardDao.java
@@ -17,23 +17,23 @@ import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
public interface CardDao extends GenericDao<Card> {
String QUERY_UPCOMING_CARDS = "SELECT c.* FROM card c " +
- "join stack s on s.localId = c.stackId " +
- "join board b on b.localId = s.boardId " +
+ "join stack s on s.localId = c.stackId " +
+ "join board b on b.localId = s.boardId " +
"WHERE b.archived = 0 and c.archived = 0 and b.status <> 3 and s.status <> 3 and c.status <> 3 " +
- "and (c.deletedAt is null or c.deletedAt = 0) " +
- "and (s.deletedAt is null or s.deletedAt = 0) " +
- "and (b.deletedAt is null or b.deletedAt = 0) " +
- // FUll Logic: (hasDueDate AND isIn_PRIVATE_Board) OR (isInSharedBoard AND (assignedToMe OR (hasDueDate AND noAssignees)))
- "and (" +
- "(c.dueDate is not null AND NOT exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3))" + //(hasDueDate AND isInPrivateBoard)
- "OR (" +
- "exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3) " + //OR (isInSharedBoard AND
- "AND (" +
- "(c.dueDate is not null AND not exists(select 1 from JoinCardWithUser j where j.cardId = c.localId)) " + // hasDueDate AND noAssignees OR
- "OR exists(select 1 from JoinCardWithUser j where j.cardId = c.localId and j.userId in (select u.localId from user u where u.uid in (select a.userName from Account a)))" + //(assignedToMe
- ")" +
- ")" +
- ")" +
+ "and (c.deletedAt is null or c.deletedAt = 0) " +
+ "and (s.deletedAt is null or s.deletedAt = 0) " +
+ "and (b.deletedAt is null or b.deletedAt = 0) " +
+ // FUll Logic: (hasDueDate AND isIn_PRIVATE_Board) OR (isInSharedBoard AND (assignedToMe OR (hasDueDate AND noAssignees)))
+ "and (" +
+ "(c.dueDate is not null AND NOT exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3))" + //(hasDueDate AND isInPrivateBoard)
+ "OR (" +
+ "exists(select 1 from AccessControl ac where ac.boardId = b.localId and ac.status <> 3) " + //OR (isInSharedBoard AND
+ "AND (" +
+ "(c.dueDate is not null AND not exists(select 1 from JoinCardWithUser j where j.cardId = c.localId)) " + // hasDueDate AND noAssignees OR
+ "OR exists(select 1 from JoinCardWithUser j where j.cardId = c.localId and j.userId in (select u.localId from user u where u.uid in (select a.userName from Account a)))" + //(assignedToMe
+ ")" +
+ ")" +
+ ")" +
"ORDER BY c.dueDate asc";
@Query("SELECT * FROM card WHERE stackId = :localStackId order by `order`, createdAt asc")
@@ -53,7 +53,8 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localId")
FullCard getFullCardByLocalIdDirectly(final long accountId, final long localId);
- @Transaction // v not deleted!
+ @Transaction
+ // v not deleted!
@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);
@@ -72,6 +73,7 @@ public interface CardDao extends GenericDao<Card> {
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCard> getFullCardByLocalId(final long accountId, final long localCardId);
+
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(final long accountId, final long localCardId);
@@ -124,4 +126,17 @@ public interface CardDao extends GenericDao<Card> {
@Transaction
@Query(QUERY_UPCOMING_CARDS)
List<FullCard> getUpcomingCardsDirectly();
+
+ @Transaction
+ @Query("SELECT c.* FROM card c " +
+ "inner join Stack s on c.stackId = s.localId " +
+ "WHERE s.boardId = :localBoardId " +
+ "and (c.title like :term or c.description like :term) " +
+ "and c.accountId = :accountId " +
+ "and s.accountId = :accountId " +
+ "and c.status <> 3 " +
+ "and s.status <> 3 " +
+ "and c.archived = 0 " +
+ "order by s.`order`, c.`order`")
+ LiveData<List<FullCard>> searchCard(long accountId, long localBoardId, String term);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
index 9a8d389c6..da0501779 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
@@ -14,7 +14,7 @@ import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
public class FilterInformation implements Serializable {
- public enum EArchiveStatus{
+ public enum EArchiveStatus {
ALL, ARCHIVED, NON_ARCHIVED
}
@@ -168,12 +168,12 @@ public class FilterInformation implements Serializable {
if (filterInformation == null) {
return false;
}
- return filterInformation.getDueType() != EDueType.NO_FILTER
- || filterInformation.getUsers().size() > 0
- || filterInformation.getProjects().size() > 0
- || filterInformation.getLabels().size() > 0
- || filterInformation.noAssignedUser
- || filterInformation.noAssignedProject
- || filterInformation.noAssignedLabel;
+ return !(filterInformation.getDueType() == EDueType.NO_FILTER
+ && filterInformation.getUsers().isEmpty()
+ && filterInformation.getProjects().isEmpty()
+ && filterInformation.getLabels().isEmpty()
+ && !filterInformation.noAssignedUser
+ && !filterInformation.noAssignedProject
+ && !filterInformation.noAssignedLabel);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/repository/BaseRepository.java b/app/src/main/java/it/niedermann/nextcloud/deck/repository/BaseRepository.java
index 981b16ec8..1964a3afa 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/repository/BaseRepository.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/repository/BaseRepository.java
@@ -14,6 +14,7 @@ import androidx.lifecycle.LiveData;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -297,6 +298,12 @@ public class BaseRepository {
return dataBaseAdapter.getAccessControlByLocalBoardId(accountId, id);
}
+ // -- Card search --
+
+ public LiveData<Map<Stack, List<FullCard>>> searchCards(final long accountId, final long localBoardId, @NonNull String term, int limit) {
+ return dataBaseAdapter.searchCards(accountId, localBoardId, term, limit);
+ }
+
// --- User search ---
public LiveData<List<User>> findProposalsForUsersToAssignForACL(final long accountId, long boardId, final int topX) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
index 7dd47213c..7f976970a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
@@ -77,9 +77,9 @@ public class AccountSwitcherDialog extends DialogFragment {
Glide.with(requireContext())
.load(currentAccount.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.currentAccountItemAvatar.getContext(), R.dimen.avatar_size)))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
- .apply(RequestOptions.circleCropTransform())
.into(binding.currentAccountItemAvatar);
applyTheme(currentAccount.getColor());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
index 253e9eb60..6d635b9d6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
@@ -35,9 +35,9 @@ public class AccountSwitcherViewHolder extends RecyclerView.ViewHolder {
binding.accountHost.setText(Uri.parse(account.getUrl()).getHost());
Glide.with(itemView.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size)))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
- .apply(RequestOptions.circleCropTransform())
.into(binding.accountItemAvatar);
itemView.setOnClickListener((v) -> onAccountClick.accept(account));
binding.delete.setVisibility(View.GONE);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
index 8cd5c6548..bb6add7f4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
@@ -83,9 +83,9 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
ownerHolder.binding.owner.setText(ac.getUser().getDisplayname());
Glide.with(ownerHolder.binding.avatar.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(ownerHolder.binding.avatar.getContext(), R.dimen.avatar_size), ac.getUser().getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(ownerHolder.binding.avatar);
break;
}
@@ -94,9 +94,9 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
final var acHolder = (AccessControlViewHolder) holder;
Glide.with(acHolder.binding.avatar.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(acHolder.binding.avatar.getContext(), R.dimen.avatar_size), ac.getUser().getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(acHolder.binding.avatar);
acHolder.binding.username.setText(ac.getUser().getDisplayname());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java
index fb2ee19b2..697b21e1e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java
@@ -137,7 +137,8 @@ public abstract class AbstractCardViewHolder extends RecyclerView.ViewHolder {
coverImagesHolder.addView(coverImageView);
Glide.with(coverImageView)
.load(new SingleSignOnUrl(account.getName(), AttachmentUtil.getThumbnailUrl(account, fullCard.getId(), coverImage, coverWidth, coverHeight)))
- .placeholder(R.color.bg_info_box)
+ .placeholder(R.drawable.ic_image_grey600_24dp)
+ .error(R.drawable.ic_image_grey600_24dp)
.into(coverImageView);
}
});
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 b49f38b0c..b3b539614 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
@@ -44,6 +44,7 @@ public class EditActivity extends AppCompatActivity {
/**
* @deprecated This is only here to maintain compatibility with {@link Version#supportsComments()}
*/
+ @Deprecated
private static final int[] tabTitles = new int[]{
R.string.card_edit_details,
R.string.card_edit_attachments,
@@ -60,6 +61,7 @@ public class EditActivity extends AppCompatActivity {
/**
* @deprecated This is only here to maintain compatibility with {@link Version#supportsComments()}
*/
+ @Deprecated
private static final int[] tabIcons = new int[]{
R.drawable.ic_home_grey600_24dp,
R.drawable.ic_attach_file_grey600_24dp,
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
index 11012bb75..286c95018 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
@@ -83,9 +83,9 @@ public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> {
Glide.with(binding.icon.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.icon.getContext(), R.dimen.avatar_size), getItem(position).getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(binding.icon);
binding.label.setText(getItem(position).getDisplayname());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
index f403fed21..7630d2d9e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
@@ -1,5 +1,9 @@
package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static it.niedermann.nextcloud.deck.util.VCardUtil.getColorBasedOnDisplayName;
+
import android.graphics.Bitmap;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
@@ -19,10 +23,6 @@ import java.util.function.BiConsumer;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
-import static android.view.View.GONE;
-import static android.view.View.VISIBLE;
-import static it.niedermann.nextcloud.deck.util.VCardUtil.getColorBasedOnDisplayName;
-
public class ContactItemViewHolder extends RecyclerView.ViewHolder {
private final ItemPickerUserBinding binding;
@@ -51,8 +51,9 @@ public class ContactItemViewHolder extends RecyclerView.ViewHolder {
binding.initials.setText(null);
Glide.with(itemView.getContext())
.load(image)
- .placeholder(R.drawable.ic_person_grey600_24dp)
.apply(RequestOptions.circleCropTransform())
+ .placeholder(R.drawable.ic_person_grey600_24dp)
+ .error(R.drawable.ic_person_grey600_24dp)
.into(binding.avatar);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java
index 346fca9c3..402329767 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java
@@ -30,6 +30,7 @@ public class GalleryItemViewHolder extends RecyclerView.ViewHolder {
Glide.with(itemView.getContext())
.load(image)
.placeholder(R.drawable.ic_image_grey600_24dp)
+ .error(R.drawable.ic_image_grey600_24dp)
.into(binding.preview);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
index c4d87740c..f46aafd1c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
@@ -93,9 +93,9 @@ public class CardCommentsFragment extends Fragment implements CommentEditedListe
binding.replyCommentCancelButton.setOnClickListener((v) -> commentsViewModel.setReplyToComment(null));
Glide.with(binding.avatar.getContext())
.load(editCardViewModel.getAccount().getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details)))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(binding.avatar);
commentsViewModel.getReplyToComment().observe(getViewLifecycleOwner(), (comment) -> {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java
index c41420147..403e26886 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java
@@ -92,9 +92,9 @@ public class CardCommentsMentionProposer implements TextWatcher {
Glide.with(avatar.getContext())
.load(account.getAvatarUrl(avatarSize, user.getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(avatar);
}
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
index dc9b8dbb5..221beea76 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
@@ -44,9 +44,9 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
public void bind(@NonNull FullDeckComment comment, @NonNull Account account, @Nullable ThemeUtils utils, @NonNull MenuInflater inflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager, @NonNull Consumer<CharSequence> editListener) {
Glide.with(binding.avatar.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size), comment.getComment().getActorId()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(binding.avatar);
final var mentions = new HashMap<String, String>(comment.getComment().getMentions().size());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java
index dd5bfa4af..989b17ef7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java
@@ -26,9 +26,9 @@ public class AssigneeViewHolder extends RecyclerView.ViewHolder {
public void bind(@NonNull Account account, @NonNull User user, @Nullable Consumer<User> onClickListener) {
Glide.with(binding.avatar.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size), user.getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(binding.avatar);
if (onClickListener != null) {
itemView.setOnClickListener((v) -> onClickListener.accept(user));
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
index 08512c5c6..582303476 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
@@ -1,7 +1,6 @@
package it.niedermann.nextcloud.deck.ui.filter;
import android.app.Dialog;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -108,7 +107,7 @@ public class FilterDialogFragment extends ThemedDialogFragment {
public void applyTheme(@ColorInt int color) {
final var utils = ThemeUtils.of(color, requireContext());
- utils.deck.themeTabLayout(binding.tabLayout, Color.TRANSPARENT);
+ utils.deck.themeTabLayoutOnTransparent(binding.tabLayout);
utils.platform.tintDrawable(requireContext(), indicator, ColorRole.PRIMARY);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
index 9c89a08a9..fec2186a1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
@@ -90,9 +90,9 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
binding.title.setText(user.getDisplayname());
Glide.with(binding.avatar.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size), user.getUid()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
- .apply(RequestOptions.circleCropTransform())
.into(binding.avatar);
itemView.setSelected(selectedUsers.contains(user));
applyTheme(color);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainActivity.java
index 20fd6c12b..3e4eed845 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainActivity.java
@@ -2,19 +2,19 @@ package it.niedermann.nextcloud.deck.ui.main;
import static java.util.Collections.emptyList;
-import android.animation.AnimatorInflater;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.Bundle;
+import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
-import android.view.inputmethod.InputMethodManager;
import android.widget.PopupMenu;
import androidx.activity.OnBackPressedCallback;
@@ -34,10 +34,13 @@ import androidx.core.splashscreen.SplashScreen;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.CustomTarget;
+import com.bumptech.glide.request.transition.Transition;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayoutMediator;
@@ -90,6 +93,8 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.ui.filter.FilterDialogFragment;
import it.niedermann.nextcloud.deck.ui.filter.FilterViewModel;
+import it.niedermann.nextcloud.deck.ui.main.search.SearchAdapter;
+import it.niedermann.nextcloud.deck.ui.main.search.SearchResults;
import it.niedermann.nextcloud.deck.ui.settings.PreferencesViewModel;
import it.niedermann.nextcloud.deck.ui.stack.DeleteStackDialogFragment;
import it.niedermann.nextcloud.deck.ui.stack.DeleteStackListener;
@@ -110,6 +115,21 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
private PreferencesViewModel preferencesViewModel;
protected MainViewModel mainViewModel;
private FilterViewModel filterViewModel;
+ private SearchAdapter searchAdapter;
+ @Nullable
+ private MutableLiveData<SearchResults> searchResults$ = null;
+ private final Observer<SearchResults> searchResultsObserver = results -> {
+ if (results.result.isEmpty()) {
+ binding.emptyContentViewSearchNoResults.setVisibility(View.VISIBLE);
+ binding.emptyContentViewSearchNoTerm.setVisibility(View.GONE);
+ binding.searchResults.setVisibility(View.GONE);
+ } else {
+ binding.emptyContentViewSearchNoResults.setVisibility(View.GONE);
+ binding.searchResults.setVisibility(View.VISIBLE);
+ }
+ this.searchAdapter.setItems(results);
+ };
+ private final ReactiveLiveData<String> searchTerm$ = new ReactiveLiveData<>();
private StackAdapter stackAdapter;
private DrawerMenuInflater<MainActivity> drawerMenuInflater;
private Menu listMenu;
@@ -124,8 +144,8 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
public void handleOnBackPressed() {
if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) {
binding.drawerLayout.closeDrawer(GravityCompat.START);
- } else if (binding.searchToolbar.getVisibility() == View.VISIBLE) {
- hideFilterTextToolbar();
+ } else if (binding.searchView.isShowing()) {
+ binding.searchView.hide();
} else {
finish();
}
@@ -164,14 +184,24 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
navigationHandler = new MainActivityNavigationHandler(this, binding.drawerLayout, mainViewModel::saveCurrentBoardId);
binding.navigationView.setNavigationItemSelectedListener(navigationHandler);
+ searchAdapter = new SearchAdapter();
+ binding.searchResults.setAdapter(searchAdapter);
+ binding.searchView.getEditText().addTextChangedListener(new OnTextChangedWatcher(value -> {
+ if (TextUtils.isEmpty(value)) {
+ binding.emptyContentViewSearchNoTerm.setVisibility(View.VISIBLE);
+ binding.emptyContentViewSearchNoResults.setVisibility(View.GONE);
+ binding.searchResults.setVisibility(View.GONE);
+ searchAdapter.setItems(new SearchResults());
+ } else {
+ binding.emptyContentViewSearchNoTerm.setVisibility(View.GONE);
+ binding.searchResults.setVisibility(View.VISIBLE);
+ searchTerm$.setValue(value);
+ }
+ }));
+
stackAdapter = new StackAdapter(this);
binding.viewPager.setAdapter(stackAdapter);
binding.viewPager.setOffscreenPageLimit(2);
- binding.filterWrapper.setOnClickListener((v) -> FilterDialogFragment.newInstance().show(getSupportFragmentManager(), FilterDialogFragment.class.getCanonicalName()));
- binding.filterText.addTextChangedListener(new OnTextChangedWatcher(filterViewModel::setFilterText));
- binding.enableSearch.setOnClickListener(v -> showFilterTextToolbar());
- binding.toolbar.setOnClickListener(v -> showFilterTextToolbar());
- binding.accountSwitcher.setOnClickListener(v -> AccountSwitcherDialog.newInstance().show(getSupportFragmentManager(), AccountSwitcherDialog.class.getSimpleName()));
headerBinding.copyDebugLogs.setOnClickListener((v) -> {
try {
@@ -199,7 +229,11 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
drawerMenuInflater = new DrawerMenuInflater<>(this, binding.navigationView.getMenu());
preferencesViewModel.isDebugModeEnabled$().observe(this, enabled -> headerBinding.copyDebugLogs.setVisibility(enabled ? View.VISIBLE : View.GONE));
- filterViewModel.hasActiveFilter().observe(this, hasActiveFilter -> binding.filterWrapper.setActivated(hasActiveFilter));
+ filterViewModel.hasActiveFilter().observe(this, hasActiveFilter -> {
+ final var menu = binding.toolbar.getMenu();
+ menu.findItem(R.id.filter).setVisible(!hasActiveFilter);
+ menu.findItem(R.id.filter_active).setVisible(hasActiveFilter);
+ });
// Flag to distinguish user initiated stack changes from stack changes derived by changing the board
final var boardChanged = new AtomicBoolean(true);
@@ -275,12 +309,22 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
}
Glide
- .with(binding.accountSwitcher.getContext())
- .load(account.getAvatarUrl(binding.accountSwitcher.getWidth()))
+ .with(binding.toolbar.getContext())
+ .load(account.getAvatarUrl(binding.toolbar.getMenu().findItem(R.id.avatar).getIcon().getIntrinsicWidth()))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
- .apply(RequestOptions.circleCropTransform())
- .into(binding.accountSwitcher);
+ .into(new CustomTarget<Drawable>() {
+ @Override
+ public void onResourceReady(@NonNull Drawable resource, @Nullable Transition<? super Drawable> transition) {
+ binding.toolbar.getMenu().findItem(R.id.avatar).setIcon(resource);
+ }
+
+ @Override
+ public void onLoadCleared(@Nullable Drawable placeholder) {
+
+ }
+ });
DeckLog.verbose("Displaying maintenance mode info for", account.getName() + ":", account.isMaintenanceEnabled());
binding.infoBox.setVisibility(account.isMaintenanceEnabled() ? View.VISIBLE : View.GONE);
@@ -340,13 +384,17 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
protected void applyBoard(@NonNull Account account, @NonNull Map<Integer, Long> navigationMap, @Nullable FullBoard currentBoard) {
DeckLog.verbose("===== Apply Board", currentBoard);
filterViewModel.clearFilterInformation(true);
+ binding.toolbar.getMenu().findItem(R.id.filter).setVisible(true);
+ binding.toolbar.getMenu().findItem(R.id.filter_active).setVisible(false);
+ binding.appBarLayout.setExpanded(true);
+
+ observeSearchTerm(account, currentBoard);
if (currentBoard == null) {
applyBoardTheme(account.getColor());
showEditButtonsIfPermissionsGranted(false, false);
- binding.toolbar.setTitle(R.string.app_name_short);
- binding.filterText.setHint(R.string.app_name_short);
+ binding.toolbar.setHint(R.string.app_name_short);
binding.fab.setText(R.string.add_board);
binding.fab.setOnClickListener(v -> {
binding.fab.hide();
@@ -356,8 +404,7 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
applyBoardTheme(currentBoard.getBoard().getColor());
showEditButtonsIfPermissionsGranted(true, currentBoard.board.isPermissionEdit());
- binding.toolbar.setTitle(currentBoard.getBoard().getTitle());
- binding.filterText.setHint(getString(R.string.search_in, currentBoard.getBoard().getTitle()));
+ binding.toolbar.setHint(getString(R.string.search_in, currentBoard.getBoard().getTitle()));
binding.fab.setText(R.string.add_list);
binding.fab.setOnClickListener(v -> {
binding.fab.hide();
@@ -373,6 +420,21 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
.ifPresent(menuItemId -> binding.navigationView.setCheckedItem(menuItemId));
}
+ binding.searchView.clearText();
+ }
+
+ private void observeSearchTerm(@NonNull Account account, @Nullable FullBoard currentBoard) {
+ if (searchResults$ != null) {
+ searchResults$.removeObserver(searchResultsObserver);
+ }
+ if (currentBoard != null) {
+ searchResults$ = searchTerm$
+ .filter(() -> binding.searchView.isShowing())
+ .flatMap(term -> new ReactiveLiveData<>(mainViewModel.searchCards(account.getId(), currentBoard.getLocalId(), term, Integer.MAX_VALUE))
+ .combineWith(() -> new MutableLiveData<>(term)))
+ .map(result -> new SearchResults(account, currentBoard.getBoard(), result.first, result.second));
+ searchResults$.observe(this, searchResultsObserver);
+ }
}
private void applyStacks(@Nullable Account account, @Nullable Long boardId, @Nullable List<Stack> stacks) {
@@ -427,11 +489,11 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
private void applyBoardTheme(@ColorInt int color) {
final var utils = ThemeUtils.of(color, this);
- utils.deck.themeFilterIndicator(this, binding.filterWrapper.getDrawable());
- utils.deck.themeTabLayout(binding.stackTitles);
+ utils.deck.themeSearchBar(binding.toolbar);
+ utils.deck.themeSearchView(binding.searchView);
+ utils.deck.themeTabLayoutOnTransparent(binding.stackTitles);
utils.material.themeExtendedFAB(binding.fab);
utils.androidx.themeSwipeRefreshLayout(binding.swipeRefreshLayout);
- utils.platform.colorEditText(binding.filterText);
binding.emptyContentViewStacks.applyTheme(color);
}
@@ -602,9 +664,20 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
}
@Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.main_menu, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
public boolean onOptionsItemSelected(MenuItem item) {
final int itemId = item.getItemId();
- if (itemId == R.id.archive_cards) {
+
+ if (itemId == R.id.filter || itemId == R.id.filter_active) {
+ FilterDialogFragment.newInstance().show(getSupportFragmentManager(), FilterDialogFragment.class.getCanonicalName());
+ } else if (itemId == R.id.avatar) {
+ AccountSwitcherDialog.newInstance().show(getSupportFragmentManager(), AccountSwitcherDialog.class.getSimpleName());
+ } else if (itemId == R.id.archive_cards) {
final var stack = stackAdapter.getItem(binding.viewPager.getCurrentItem());
final var stackLocalId = stack.getLocalId();
mainViewModel.countCardsInStack(stack.getAccountId(), stackLocalId, numberOfCards -> runOnUiThread(() ->
@@ -707,27 +780,6 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
binding.fab.extend();
}
- private void showFilterTextToolbar() {
- binding.toolbar.setVisibility(View.GONE);
- binding.searchToolbar.setVisibility(View.VISIBLE);
- binding.searchToolbar.setNavigationOnClickListener(v1 -> onBackPressedCallback.handleOnBackPressed());
- binding.enableSearch.setVisibility(View.GONE);
- binding.filterText.requestFocus();
- final var imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(binding.filterText, InputMethodManager.SHOW_IMPLICIT);
- binding.toolbarCard.setStateListAnimator(AnimatorInflater.loadStateListAnimator(this, R.animator.appbar_elevation_on));
- }
-
- private void hideFilterTextToolbar() {
- binding.filterText.setText(null);
- final var imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
- imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
- binding.searchToolbar.setVisibility(View.GONE);
- binding.enableSearch.setVisibility(View.VISIBLE);
- binding.toolbar.setVisibility(View.VISIBLE);
- binding.toolbarCard.setStateListAnimator(AnimatorInflater.loadStateListAnimator(this, R.animator.appbar_elevation_off));
- }
-
private void registerAutoSyncOnNetworkAvailable(@NonNull Account account) {
final var connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
final var builder = new NetworkRequest.Builder();
@@ -912,5 +964,4 @@ public class MainActivity extends AppCompatActivity implements DeleteStackListen
.newInstance(throwable, account)
.show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
-
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainViewModel.java
index 25e43fe4c..e9afe5419 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/MainViewModel.java
@@ -16,6 +16,7 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundExce
import java.io.File;
import java.time.Instant;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
import it.niedermann.android.reactivelivedata.ReactiveLiveData;
@@ -58,6 +59,10 @@ public class MainViewModel extends BaseViewModel {
return new IllegalStateException("SyncManager is null");
}
+ public LiveData<Map<Stack, List<FullCard>>> searchCards(long accountId, long boardId, @NonNull String term, int limit) {
+ return baseRepository.searchCards(accountId, boardId, term, limit);
+ }
+
public void synchronize(@NonNull Account account, @NonNull IResponseCallback<Boolean> callback) {
if (syncRepository == null) {
callback.onError(getInvalidSyncManagerException());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchAdapter.java
new file mode 100644
index 000000000..cd107e629
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchAdapter.java
@@ -0,0 +1,140 @@
+package it.niedermann.nextcloud.deck.ui.main.search;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.databinding.ItemSearchCardBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemSearchStackBinding;
+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.FullCard;
+import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
+
+public class SearchAdapter extends RecyclerView.Adapter<SearchViewHolder> {
+
+ private static final int TYPE_STACK = 0;
+ private static final int TYPE_CARD = 1;
+
+ @Nullable
+ private Account account;
+ @Nullable
+ private Board board;
+ private final List<IRemoteEntity> items = new ArrayList<>();
+ @NonNull
+ private String term = "";
+
+ @NonNull
+ @Override
+ public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ final var context = parent.getContext();
+ switch (viewType) {
+ case TYPE_STACK: {
+ return new SearchStackViewHolder(ItemSearchStackBinding.inflate(LayoutInflater.from(context), parent, false));
+ }
+ case TYPE_CARD: {
+ return new SearchCardViewHolder(ItemSearchCardBinding.inflate(LayoutInflater.from(context), parent, false));
+ }
+ default: {
+ throw new UnsupportedOperationException("Unknown view type: " + viewType);
+ }
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case TYPE_STACK: {
+ final var localId = -getItemId(position);
+ items.stream()
+ .filter(item -> item.getClass() == Stack.class)
+ .filter(item -> item.getLocalId() == localId)
+ .findAny()
+ .map(item -> (Stack) item)
+ .ifPresent(stack -> {
+ final var searchStackViewHolder = (SearchStackViewHolder) holder;
+ searchStackViewHolder.bind(stack);
+
+ if (board == null) {
+ DeckLog.logError(new IllegalStateException("board is null"));
+ return;
+ }
+ searchStackViewHolder.applyTheme(board.getColor());
+ });
+ break;
+ }
+ case TYPE_CARD: {
+ if (account == null || board == null) {
+ DeckLog.logError(new IllegalStateException("account or board is null"));
+ break;
+ }
+ final var localId = getItemId(position);
+ items.stream()
+ .filter(item -> item.getClass() == FullCard.class)
+ .filter(item -> item.getLocalId() == localId)
+ .findAny()
+ .map(item -> (FullCard) item)
+ .ifPresent(fullCard -> {
+ final var searchCardViewHolder = (SearchCardViewHolder) holder;
+ searchCardViewHolder.bind(account, board.getLocalId(), fullCard);
+ searchCardViewHolder.applyTheme(board.getColor(), term);
+ });
+ break;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unknown view type for position " + position);
+ }
+ }
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return getItemId(position) > 0 ? TYPE_CARD : TYPE_STACK;
+ }
+
+ /**
+ * @return {@link FullCard#getLocalId()} or <strong>negated</strong> {@link Stack#getLocalId()}
+ */
+ @Override
+ public long getItemId(int position) {
+ final var item = items.get(position);
+ final var clazz = item.getClass();
+ if (clazz == Stack.class) {
+ return -item.getLocalId();
+ } else if (clazz == FullCard.class) {
+ return item.getLocalId();
+ }
+ throw new UnsupportedOperationException("Expected item list to only contain " + Stack.class.getSimpleName() + " or " + FullCard.class.getSimpleName() + " but found " + clazz.getSimpleName());
+ }
+
+ @Override
+ public int getItemCount() {
+ return items.size();
+ }
+
+ public void setItems(@NonNull SearchResults results) {
+ this.account = results.account;
+ this.board = results.board;
+ this.term = results.term;
+
+ this.items.clear();
+ results.result.entrySet()
+ .stream()
+ .sorted(Comparator.comparingLong(o -> o.getKey().getOrder()))
+ .forEach(entry -> {
+ this.items.add(entry.getKey());
+ this.items.addAll(entry.getValue());
+ });
+
+ notifyDataSetChanged();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchCardViewHolder.java
new file mode 100644
index 000000000..9e088f3da
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchCardViewHolder.java
@@ -0,0 +1,74 @@
+package it.niedermann.nextcloud.deck.ui.main.search;
+
+import android.text.TextUtils;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.resource.bitmap.CenterCrop;
+import com.bumptech.glide.load.resource.bitmap.RoundedCorners;
+import com.bumptech.glide.request.RequestOptions;
+import com.nextcloud.android.common.ui.theme.utils.ColorRole;
+
+import it.niedermann.android.util.DimensionUtil;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemSearchCardBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils;
+import it.niedermann.nextcloud.deck.util.AttachmentUtil;
+import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
+import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
+
+public class SearchCardViewHolder extends SearchViewHolder {
+
+ private final ItemSearchCardBinding binding;
+
+ public SearchCardViewHolder(@NonNull ItemSearchCardBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Account account, long localBoardId, @NonNull FullCard fullCard) {
+ final var context = binding.getRoot().getContext();
+ binding.getRoot().setOnClickListener(v -> context.startActivity(EditActivity.createEditCardIntent(context, account, localBoardId, fullCard.getLocalId())));
+
+ binding.title.setText(fullCard.getCard().getTitle());
+ if (TextUtils.isEmpty(fullCard.getCard().getDescription())) {
+ binding.description.setVisibility(View.GONE);
+ } else {
+ binding.description.setVisibility(View.VISIBLE);
+ binding.description.setText(fullCard.getCard().getDescription());
+ }
+
+
+ final var coverImages = fullCard.getAttachments()
+ .stream()
+ .filter(attachment -> MimeTypeUtil.isImage(attachment.getMimetype()))
+ .findFirst();
+
+ if (coverImages.isPresent()) {
+ binding.coverImages.setVisibility(View.VISIBLE);
+ binding.coverImages.post(() -> Glide.with(binding.coverImages)
+ .load(new SingleSignOnUrl(account.getName(), AttachmentUtil.getThumbnailUrl(account, fullCard.getId(), coverImages.get(), binding.coverImages.getWidth())))
+ .apply(new RequestOptions().transform(
+ new CenterCrop(),
+ new RoundedCorners(DimensionUtil.INSTANCE.dpToPx(context, R.dimen.spacer_1x))
+ ))
+ .placeholder(R.drawable.ic_image_grey600_24dp)
+ .error(R.drawable.ic_image_grey600_24dp)
+ .into(binding.coverImages));
+ } else {
+ binding.coverImages.setVisibility(View.GONE);
+ }
+ }
+
+ public void applyTheme(int color, String term) {
+ final var utils = ThemeUtils.of(color, binding.getRoot().getContext());
+ utils.platform.colorTextView(binding.title, ColorRole.ON_SURFACE);
+ utils.platform.highlightText(binding.title, binding.title.getText().toString(), term);
+ utils.platform.highlightText(binding.description, binding.description.getText().toString(), term);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchResults.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchResults.java
new file mode 100644
index 000000000..128fd8c63
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchResults.java
@@ -0,0 +1,39 @@
+package it.niedermann.nextcloud.deck.ui.main.search;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+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.FullCard;
+
+public class SearchResults {
+
+ @Nullable
+ public final Account account;
+
+ @Nullable
+ public final Board board;
+
+ @NonNull
+ public final Map<Stack, List<FullCard>> result;
+
+ @NonNull
+ public final String term;
+
+ public SearchResults() {
+ this(null, null, Collections.emptyMap(), "");
+ }
+
+ public SearchResults(@Nullable Account account, @Nullable Board board, @NonNull Map<Stack, List<FullCard>> result, @NonNull String term) {
+ this.account = account;
+ this.board = board;
+ this.result = result;
+ this.term = term;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchStackViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchStackViewHolder.java
new file mode 100644
index 000000000..027952372
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchStackViewHolder.java
@@ -0,0 +1,28 @@
+package it.niedermann.nextcloud.deck.ui.main.search;
+
+import androidx.annotation.NonNull;
+
+import com.nextcloud.android.common.ui.theme.utils.ColorRole;
+
+import it.niedermann.nextcloud.deck.databinding.ItemSearchStackBinding;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils;
+
+public class SearchStackViewHolder extends SearchViewHolder {
+
+ private final ItemSearchStackBinding binding;
+
+ public SearchStackViewHolder(@NonNull ItemSearchStackBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Stack stack) {
+ binding.title.setText(stack.getTitle());
+ }
+
+ public void applyTheme(int color) {
+ final var utils = ThemeUtils.of(color, binding.getRoot().getContext());
+ utils.platform.colorTextView(binding.title, ColorRole.ON_SURFACE_VARIANT);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchViewHolder.java
new file mode 100644
index 000000000..f787beed4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/main/search/SearchViewHolder.java
@@ -0,0 +1,13 @@
+package it.niedermann.nextcloud.deck.ui.main.search;
+
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+public abstract class SearchViewHolder extends RecyclerView.ViewHolder {
+
+ public SearchViewHolder(@NonNull View itemView) {
+ super(itemView);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
index 79ba73c32..c4684fe55 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
@@ -39,9 +39,9 @@ public class ManageAccountViewHolder extends RecyclerView.ViewHolder {
binding.accountHost.setText(Uri.parse(account.getUrl()).getHost());
Glide.with(itemView.getContext())
.load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size)))
+ .apply(RequestOptions.circleCropTransform())
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
- .apply(RequestOptions.circleCropTransform())
.into(binding.accountItemAvatar);
binding.currentAccountIndicator.setSelected(isCurrentAccount);
itemView.setOnClickListener((v) -> onAccountClick.accept(account));
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/theme/DeckViewThemeUtils.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/theme/DeckViewThemeUtils.java
index db0134600..b325388b2 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/theme/DeckViewThemeUtils.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/theme/DeckViewThemeUtils.java
@@ -1,6 +1,7 @@
package it.niedermann.nextcloud.deck.ui.theme;
import static com.nextcloud.android.common.ui.util.ColorStateListUtilsKt.buildColorStateList;
+import static com.nextcloud.android.common.ui.util.PlatformThemeUtil.isDarkMode;
import static java.time.temporal.ChronoUnit.DAYS;
import android.content.Context;
@@ -24,6 +25,8 @@ import androidx.core.content.res.ResourcesCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.widget.TextViewCompat;
+import com.google.android.material.search.SearchBar;
+import com.google.android.material.search.SearchView;
import com.google.android.material.tabs.TabLayout;
import com.nextcloud.android.common.ui.theme.MaterialSchemes;
import com.nextcloud.android.common.ui.theme.ViewThemeUtilsBase;
@@ -58,21 +61,53 @@ public class DeckViewThemeUtils extends ViewThemeUtilsBase {
}
/**
+ * Themes the <code>tabLayout</code> using {@link MaterialViewThemeUtils#themeTabLayout(TabLayout)}
+ * and then applies <code>null</code> as {@link TabLayout#setBackground(Drawable)}.
+ */
+ public void themeTabLayoutOnTransparent(@NonNull TabLayout tabLayout) {
+ this.material.themeTabLayout(tabLayout);
+ tabLayout.setBackground(null);
+ }
+
+ /**
* Convenience method for calling {@link #themeTabLayout(TabLayout, int)} with the primary color
*/
public void themeTabLayout(@NonNull TabLayout tabLayout) {
themeTabLayout(tabLayout, ContextCompat.getColor(tabLayout.getContext(), R.color.primary));
}
- /**
- * Themes the <code>tabLayout</code> using {@link MaterialViewThemeUtils#themeTabLayout(TabLayout)}
- * and then applies <code>backgroundColor</code>.
- */
public void themeTabLayout(@NonNull TabLayout tabLayout, @ColorInt int backgroundColor) {
this.material.themeTabLayout(tabLayout);
tabLayout.setBackgroundColor(backgroundColor);
}
+ public void themeSearchBar(@NonNull SearchBar searchBar) {
+ withScheme(searchBar.getContext(), scheme -> {
+ final var colorStateList = ColorStateList.valueOf(
+ isDarkMode(searchBar.getContext())
+ ? scheme.getSurface()
+ : scheme.getSurfaceVariant());
+
+ searchBar.setBackgroundTintList(colorStateList);
+
+ final var menu = searchBar.getMenu();
+ for (int i = 0; i < menu.size(); i++) {
+ if (menu.getItem(i).getItemId() != R.id.avatar) {
+ platform.colorToolbarMenuIcon(searchBar.getContext(), menu.getItem(i));
+ }
+ }
+
+ return searchBar;
+ });
+ }
+
+ public void themeSearchView(@NonNull SearchView searchView) {
+ withScheme(searchView.getContext(), scheme -> {
+ searchView.setBackgroundTintList(ColorStateList.valueOf(scheme.getSurface()));
+ return searchView;
+ });
+ }
+
public Drawable themeNavigationViewIcon(@NonNull Context context, @DrawableRes int icon) {
return withScheme(context, scheme -> {
final var colorStateList = buildColorStateList(
@@ -110,14 +145,6 @@ public class DeckViewThemeUtils extends ViewThemeUtilsBase {
.ifPresent(drawable -> platform.tintDrawable(context, drawable, ColorRole.PRIMARY));
}
- /**
- * Use <strong>only</strong> for <code>@drawable/filter</code>
- */
- public void themeFilterIndicator(@NonNull Context context, @NonNull Drawable filter) {
- getStateDrawable(filter, android.R.attr.state_activated, R.id.indicator)
- .ifPresent(drawable -> platform.tintDrawable(context, drawable, ColorRole.PRIMARY));
- }
-
private Optional<Drawable> getStateDrawable(@NonNull Drawable drawable, @AttrRes int state, @IdRes int layerId) {
return getStateDrawable(drawable, new int[]{state}, layerId);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/FilterIndicator.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/FilterIndicator.java
new file mode 100644
index 000000000..73b3a84a6
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/FilterIndicator.java
@@ -0,0 +1,27 @@
+package it.niedermann.nextcloud.deck.ui.view;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.button.MaterialButton;
+
+import it.niedermann.nextcloud.deck.R;
+
+public class FilterIndicator extends MaterialButton {
+
+ public FilterIndicator(Context context) {
+ this(context, null, 0);
+ }
+
+ public FilterIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public FilterIndicator(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, R.style.ThemeOverlay_Material3_Button_IconButton);
+ setIcon(ContextCompat.getDrawable(context, R.drawable.filter_active));
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/res/drawable/filter.xml b/app/src/main/res/drawable/filter.xml
deleted file mode 100644
index a3933e864..000000000
--- a/app/src/main/res/drawable/filter.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_activated="true">
- <layer-list>
- <item android:drawable="@drawable/ic_filter_list_white_24dp" />
- <item android:id="@+id/indicator" android:width="8dp" android:height="8dp" android:enterFadeDuration="@android:integer/config_shortAnimTime" android:gravity="bottom|end">
- <shape android:shape="oval">
- <solid android:color="@color/defaultBrand" />
- </shape>
- </item>
- </layer-list>
- </item>
- <item android:drawable="@drawable/ic_filter_list_white_24dp" />
-</selector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/filter_active.xml b/app/src/main/res/drawable/filter_active.xml
new file mode 100644
index 000000000..06b74eb85
--- /dev/null
+++ b/app/src/main/res/drawable/filter_active.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:drawable="@drawable/ic_filter_list_white_24dp" />
+ <item
+ android:width="8dp"
+ android:height="8dp"
+ android:enterFadeDuration="@android:integer/config_shortAnimTime"
+ android:gravity="bottom|end">
+ <shape android:shape="oval">
+ <solid android:color="?attr/colorOnSurface" />
+ </shape>
+ </item>
+</layer-list>
diff --git a/app/src/main/res/drawable/ic_filter_list_white_24dp.xml b/app/src/main/res/drawable/ic_filter_list_white_24dp.xml
index 5c0bcb912..46fabab9c 100644
--- a/app/src/main/res/drawable/ic_filter_list_white_24dp.xml
+++ b/app/src/main/res/drawable/ic_filter_list_white_24dp.xml
@@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
- android:tint="#FFFFFF" android:viewportHeight="24.0"
+ android:tint="?attr/colorOnSurface" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
</vector>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index d194335db..e05c9b9a1 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -13,181 +13,14 @@
android:layout_height="match_parent"
tools:context=".ui.main.MainActivity">
- <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
- android:id="@+id/swipe_refresh_layout"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/info_box"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal|bottom"
- android:background="@color/bg_info_box"
- android:gravity="center"
- android:padding="@dimen/spacer_1hx"
- android:visibility="gone"
- tools:visibility="visible">
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:drawablePadding="@dimen/spacer_1hx"
- android:gravity="center"
- android:paddingHorizontal="@dimen/spacer_1hx"
- android:text="@string/info_box_maintenance_mode"
- android:textColor="@color/grey600"
- app:drawableStartCompat="@drawable/ic_info_outline_grey600_24dp" />
-
- </LinearLayout>
-
- <TextView
- android:id="@+id/info_box_version_not_supported"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@color/danger"
- android:drawablePadding="@dimen/spacer_1hx"
- android:gravity="center"
- android:paddingHorizontal="@dimen/spacer_2x"
- android:paddingVertical="@dimen/spacer_1x"
- android:text="@string/info_box_version_not_supported"
- android:textColor="@android:color/white"
- android:textSize="14sp"
- android:visibility="gone"
- app:drawableStartCompat="@drawable/ic_warning_white_24dp"
- tools:visibility="visible" />
-
- <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
- android:id="@+id/empty_content_view_boards"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- app:description="@string/add_a_new_board_using_the_button"
- app:title="@string/no_boards" />
-
- <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
- android:id="@+id/empty_content_view_stacks"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:visibility="gone"
- app:description="@string/add_a_new_list_using_the_button"
- app:title="@string/no_lists_yet"
- tools:visibility="visible" />
-
- <androidx.viewpager2.widget.ViewPager2
- android:id="@+id/viewPager"
- android:layout_width="match_parent"
- android:layout_height="match_parent" />
- </LinearLayout>
- </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
-
- <com.google.android.material.appbar.AppBarLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?attr/colorPrimary">
-
- <com.google.android.material.card.MaterialCardView
- android:id="@+id/toolbarCard"
- android:layout_width="match_parent"
- android:layout_height="?attr/actionBarSize"
- android:layout_marginHorizontal="@dimen/spacer_2x"
- android:layout_marginTop="@dimen/spacer_1x"
- app:cardCornerRadius="@dimen/spacer_4x"
- app:cardElevation="0dp"
- app:strokeWidth="0dp">
-
- <androidx.constraintlayout.widget.ConstraintLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="?attr/colorPrimary"
- tools:title="Deck">
-
- <com.google.android.material.appbar.MaterialToolbar
- android:id="@+id/toolbar"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/enableSearch"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent" />
-
- <com.google.android.material.appbar.MaterialToolbar
- android:id="@+id/searchToolbar"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
- android:visibility="gone"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/enableSearch"
- app:layout_constraintStart_toStartOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:navigationIcon="@drawable/ic_arrow_back_white_24dp">
-
- <EditText
- android:id="@+id/filter_text"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="@android:color/transparent"
- android:hint="@string/simple_filter"
- android:inputType="text"
- android:maxLines="1"
- tools:hint="@string/app_name_short" />
- </com.google.android.material.appbar.MaterialToolbar>
-
- <ImageButton
- android:id="@+id/enableSearch"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:background="@null"
- android:contentDescription="@string/simple_search"
- android:paddingHorizontal="@dimen/spacer_1hx"
- android:tooltipText="@string/simple_search"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/filterWrapper"
- app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/ic_baseline_search_24"
- app:tint="?attr/colorAccent"
- tools:targetApi="o" />
-
- <ImageButton
- android:id="@+id/filterWrapper"
- android:layout_width="wrap_content"
- android:layout_height="0dp"
- android:background="?selectableItemBackgroundBorderless"
- android:contentDescription="@string/simple_filter"
- android:paddingHorizontal="@dimen/spacer_1hx"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toStartOf="@id/accountSwitcher"
- app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/filter"
- app:tint="?attr/colorAccent" />
-
- <ImageView
- android:id="@+id/accountSwitcher"
- android:layout_width="44dp"
- android:layout_height="0dp"
- android:layout_marginEnd="@dimen/spacer_1hx"
- android:background="?attr/selectableItemBackgroundBorderless"
- android:contentDescription="@string/choose_account"
- android:padding="@dimen/spacer_1hx"
- android:tooltipText="@string/choose_account"
- app:layout_constraintBottom_toBottomOf="parent"
- app:layout_constraintEnd_toEndOf="parent"
- app:layout_constraintTop_toTopOf="parent"
- app:srcCompat="@drawable/ic_baseline_account_circle_24"
- tools:targetApi="o" />
- </androidx.constraintlayout.widget.ConstraintLayout>
- </com.google.android.material.card.MaterialCardView>
-
- <LinearLayout
- android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
@@ -214,6 +47,93 @@
tools:ignore="UnusedAttribute" />
</LinearLayout>
+ <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
+ android:id="@+id/swipe_refresh_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/info_box"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|bottom"
+ android:background="@color/bg_info_box"
+ android:gravity="center"
+ android:padding="@dimen/spacer_1hx"
+ android:visibility="gone"
+ tools:visibility="visible">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:drawablePadding="@dimen/spacer_1hx"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/spacer_1hx"
+ android:text="@string/info_box_maintenance_mode"
+ android:textColor="@color/grey600"
+ app:drawableStartCompat="@drawable/ic_info_outline_grey600_24dp" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/info_box_version_not_supported"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/danger"
+ android:drawablePadding="@dimen/spacer_1hx"
+ android:gravity="center"
+ android:paddingHorizontal="@dimen/spacer_2x"
+ android:paddingVertical="@dimen/spacer_1x"
+ android:text="@string/info_box_version_not_supported"
+ android:textColor="@android:color/white"
+ android:textSize="14sp"
+ android:visibility="gone"
+ app:drawableStartCompat="@drawable/ic_warning_white_24dp"
+ tools:visibility="visible" />
+
+ <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
+ android:id="@+id/empty_content_view_boards"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ app:description="@string/add_a_new_board_using_the_button"
+ app:title="@string/no_boards" />
+
+ <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
+ android:id="@+id/empty_content_view_stacks"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ app:description="@string/add_a_new_list_using_the_button"
+ app:title="@string/no_lists_yet"
+ tools:visibility="visible" />
+
+ <androidx.viewpager2.widget.ViewPager2
+ android:id="@+id/viewPager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+ </LinearLayout>
+ </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
+ </LinearLayout>
+
+ <com.google.android.material.appbar.AppBarLayout
+ android:id="@+id/appBarLayout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorPrimary">
+
+ <com.google.android.material.search.SearchBar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:menu="@menu/main_menu"
+ app:navigationIcon="@drawable/ic_arrow_back_white_24dp" />
+
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
@@ -226,6 +146,41 @@
android:visibility="gone"
app:icon="@drawable/ic_add_white_24dp" />
+ <com.google.android.material.search.SearchView
+ android:id="@+id/search_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:hint="@string/simple_search"
+ app:layout_anchor="@id/toolbar"
+ app:useDrawerArrowDrawable="true">
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/search_results"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:itemCount="10"
+ tools:listitem="@layout/item_search_card" />
+
+ <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
+ android:id="@+id/empty_content_view_search_no_term"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ app:description="@string/enter_search_term_description"
+ app:image="@drawable/ic_baseline_search_24"
+ app:title="@string/enter_search_term_title" />
+
+ <it.niedermann.nextcloud.deck.ui.view.EmptyContentView
+ android:id="@+id/empty_content_view_search_no_results"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ app:description="@string/no_search_results_description"
+ app:image="@drawable/ic_baseline_search_24"
+ app:title="@string/no_search_results_title" />
+ </com.google.android.material.search.SearchView>
+
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView
diff --git a/app/src/main/res/layout/item_search_card.xml b/app/src/main/res/layout/item_search_card.xml
new file mode 100644
index 000000000..9672259d1
--- /dev/null
+++ b/app/src/main/res/layout/item_search_card.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:paddingHorizontal="@dimen/spacer_4x"
+ android:paddingVertical="@dimen/spacer_2x">
+
+ <ImageView
+ android:id="@+id/coverImages"
+ android:layout_width="@dimen/avatar_size"
+ android:layout_height="@dimen/avatar_size"
+ android:contentDescription="@null"
+ android:scaleType="centerCrop"
+ android:src="@null"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ tools:src="@tools:sample/backgrounds/scenic" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_2x"
+ android:ellipsize="end"
+ android:maxLines="1"
+ android:textSize="18sp"
+ app:layout_constraintBottom_toTopOf="@id/description"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/coverImages"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_goneMarginStart="0dp"
+ tools:text="@tools:sample/lorem" />
+
+ <com.google.android.material.textview.MaterialTextView
+ android:id="@+id/description"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_2x"
+ android:layout_marginTop="@dimen/spacer_1hx"
+ android:ellipsize="end"
+ android:maxLines="1"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/coverImages"
+ app:layout_constraintTop_toBottomOf="@id/title"
+ app:layout_goneMarginStart="0dp"
+ tools:text="@tools:sample/lorem" />
+</androidx.constraintlayout.widget.ConstraintLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_stack.xml b/app/src/main/res/layout/item_search_stack.xml
new file mode 100644
index 000000000..19e2b7da2
--- /dev/null
+++ b/app/src/main/res/layout/item_search_stack.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="@dimen/spacer_4x"
+ android:paddingTop="@dimen/spacer_4x"
+ android:paddingEnd="@dimen/spacer_4x"
+ android:paddingBottom="@dimen/spacer_2x"
+ android:textColor="?attr/colorAccent"
+ android:textSize="22sp"
+ android:textStyle="bold"
+ tools:text="@tools:sample/lorem" /> \ No newline at end of file
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 000000000..7c0d51182
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/filter"
+ android:icon="@drawable/ic_filter_list_white_24dp"
+ android:title="@string/simple_filter"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/filter_active"
+ android:icon="@drawable/filter_active"
+ android:title="@string/simple_filter"
+ android:visible="false"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/avatar"
+ android:icon="@drawable/ic_baseline_account_circle_24"
+ android:title="@string/choose_account"
+ app:showAsAction="ifRoom" />
+</menu>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e51f75722..aa4b10b15 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -355,4 +355,8 @@
<string name="show_error">Show error</string>
<string name="push_notification_link_empty">Due to a known issue in the Deck web app we are unfortunately not able to display this card. For more information see: %1$s</string>
<string name="push_notification_link_empty_link" translatable="false">https://github.com/nextcloud/deck/issues/3431</string>
+ <string name="enter_search_term_title">Enter search term</string>
+ <string name="enter_search_term_description">Enter search term to find cards in this board</string>
+ <string name="no_search_results_title">No search results</string>
+ <string name="no_search_results_description">We haven\'t found any result for the given search term</string>
</resources>
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/database/DataBaseAdapterTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/database/DataBaseAdapterTest.java
index 50c093ebf..8e1018b96 100644
--- a/app/src/test/java/it/niedermann/nextcloud/deck/database/DataBaseAdapterTest.java
+++ b/app/src/test/java/it/niedermann/nextcloud/deck/database/DataBaseAdapterTest.java
@@ -1,13 +1,17 @@
package it.niedermann.nextcloud.deck.database;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import static java.lang.reflect.Modifier.isProtected;
import static it.niedermann.nextcloud.deck.database.DeckDatabaseTestUtil.createAccount;
import static it.niedermann.nextcloud.deck.database.DeckDatabaseTestUtil.createBoard;
+import static it.niedermann.nextcloud.deck.database.DeckDatabaseTestUtil.createCard;
+import static it.niedermann.nextcloud.deck.database.DeckDatabaseTestUtil.createStack;
import static it.niedermann.nextcloud.deck.database.DeckDatabaseTestUtil.createUser;
import android.content.Context;
+import androidx.annotation.NonNull;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
@@ -25,8 +29,15 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Objects;
import java.util.concurrent.ExecutorService;
+import it.niedermann.nextcloud.deck.TestUtil;
+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.interfaces.IRemoteEntity;
@RunWith(RobolectricTestRunner.class)
@@ -111,4 +122,50 @@ public class DataBaseAdapterTest {
assertEquals(leet + 1, args.get(1));
}
+ @Test
+ public void testSearchCards() throws InterruptedException {
+ final var account = createAccount(db.getAccountDao());
+ final var user = createUser(db.getUserDao(), account);
+ final var board = createBoard(db.getBoardDao(), account, user);
+
+ final var stack1 = createStack(db.getStackDao(), account, board);
+ final var stack2 = createStack(db.getStackDao(), account, board);
+ final var card1_1 = createCard(db.getCardDao(), account, stack1, "Foo", "Hello world");
+ final var card1_2 = createCard(db.getCardDao(), account, stack1, "Bar", "Hello Bar");
+ final var card1_3 = createCard(db.getCardDao(), account, stack2, "Baz", "");
+ final var card2_1 = createCard(db.getCardDao(), account, stack2, "Qux", "Hello Foo");
+ final var card2_2 = createCard(db.getCardDao(), account, stack2, "Lorem", "Ipsum");
+
+ var result = TestUtil.getOrAwaitValue(adapter.searchCards(account.getId(), board.getLocalId(), "Hello", 3));
+ assertEquals(2, result.size());
+ assertEquals(2, countCardsOf(result, stack1));
+ assertEquals(1, countCardsOf(result, stack2));
+ assertTrue(containsCard(result, stack1, card1_1));
+ assertTrue(containsCard(result, stack1, card1_2));
+ assertTrue(containsCard(result, stack2, card2_1));
+ }
+
+ private int countCardsOf(@NonNull Map<Stack, List<FullCard>> map, @NonNull Stack stackToFind) {
+ for (final var stack : map.keySet()) {
+ if (Objects.equals(stack.getLocalId(), stackToFind.getLocalId())) {
+ //noinspection ConstantConditions
+ return map.get(stack).size();
+ }
+ }
+ throw new NoSuchElementException();
+ }
+
+ private boolean containsCard(@NonNull Map<Stack, List<FullCard>> map, @NonNull Stack stackToFind, @NonNull Card cardToFind) {
+ for (final var stack : map.keySet()) {
+ if (Objects.equals(stack.getLocalId(), stackToFind.getLocalId())) {
+ //noinspection ConstantConditions
+ for (final var fullCard : map.get(stack)) {
+ if (Objects.equals(fullCard.getLocalId(), cardToFind.getLocalId())) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
}
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/database/DeckDatabaseTestUtil.java b/app/src/test/java/it/niedermann/nextcloud/deck/database/DeckDatabaseTestUtil.java
index 8da72d4c6..42e6f4d9d 100644
--- a/app/src/test/java/it/niedermann/nextcloud/deck/database/DeckDatabaseTestUtil.java
+++ b/app/src/test/java/it/niedermann/nextcloud/deck/database/DeckDatabaseTestUtil.java
@@ -62,10 +62,14 @@ public class DeckDatabaseTestUtil {
}
public static Card createCard(@NonNull CardDao dao, @NonNull Account account, @NonNull Stack stack) {
+ return createCard(dao, account, stack, randomString(15), randomString(50));
+ }
+
+ public static Card createCard(@NonNull CardDao dao, @NonNull Account account, @NonNull Stack stack, @NonNull String title, @NonNull String description) {
final var cardToCreate = new Card();
cardToCreate.setAccountId(account.getId());
- cardToCreate.setTitle(randomString(15));
- cardToCreate.setDescription(randomString(50));
+ cardToCreate.setTitle(title);
+ cardToCreate.setDescription(description);
cardToCreate.setStackId(stack.getLocalId());
cardToCreate.setId(currentLong++);
diff --git a/fastlane/metadata/android/en-US/changelogs/1021009.txt b/fastlane/metadata/android/en-US/changelogs/1021009.txt
index 90074559e..4b758f405 100644
--- a/fastlane/metadata/android/en-US/changelogs/1021009.txt
+++ b/fastlane/metadata/android/en-US/changelogs/1021009.txt
@@ -1,3 +1,4 @@
+- 🔍 Implement new SearchBar
- 🎨 Unify material theming with Nextcloud files app
- ↔️ Improve 'Move Card' Dialog (#972)
- 🐞 Fix collapsed input fields of dialogs (#1385)