diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java index 9ec2eac3..67feb6bb 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/NotesListViewItemTouchHelper.java @@ -37,8 +37,8 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper { @NonNull ISyncCallback syncCallBack, @NonNull Runnable refreshLists, @Nullable SwipeRefreshLayout swipeRefreshLayout, - @Nullable ViewProvider viewProvider - ) { + @Nullable ViewProvider viewProvider, + boolean gridView) { super(new SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { private boolean swipeRefreshLayoutEnabled; @@ -56,7 +56,7 @@ public class NotesListViewItemTouchHelper extends ItemTouchHelper { */ @Override public int getSwipeDirs(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof SectionViewHolder) return 0; + if (gridView || viewHolder instanceof SectionViewHolder) return 0; return super.getSwipeDirs(recyclerView, viewHolder); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java index 3daf809a..3523e35c 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/activity/NotesListViewActivity.java @@ -13,6 +13,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; +import android.util.DisplayMetrics; import android.util.Log; import android.view.View; import android.view.ViewTreeObserver; @@ -27,6 +28,7 @@ import androidx.core.view.GravityCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.bumptech.glide.Glide; @@ -90,6 +92,8 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi private static final String TAG = NotesListViewActivity.class.getSimpleName(); + public static final boolean FEATURE_TOGGLE_GRID_VIEW = true; + public static final String CREATED_NOTE = "it.niedermann.owncloud.notes.created_notes"; public static final String ADAPTER_KEY_RECENT = "recent"; public static final String ADAPTER_KEY_STARRED = "starred"; @@ -229,7 +233,8 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi try { BrandingUtil.saveBrandColors(this, localAccount.getColor(), localAccount.getTextColor()); ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(getApplicationContext()); - new NotesListViewItemTouchHelper(ssoAccount, this, db, adapter, syncCallBack, this::refreshLists, swipeRefreshLayout, this).attachToRecyclerView(listView); + new NotesListViewItemTouchHelper(ssoAccount, this, db, adapter, syncCallBack, this::refreshLists, swipeRefreshLayout, this, FEATURE_TOGGLE_GRID_VIEW) + .attachToRecyclerView(listView); synchronize(); } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) { Log.i(TAG, "Tried to select account, but got an " + e.getClass().getSimpleName() + ". Asking for importing an account..."); @@ -588,10 +593,17 @@ public class NotesListViewActivity extends LockedActivity implements NoteClickLi binding.navigationMenu.setAdapter(adapterMenu); } - public void initList() { - adapter = new ItemAdapter(this); + private void initList() { + adapter = new ItemAdapter(this, FEATURE_TOGGLE_GRID_VIEW); listView.setAdapter(adapter); - listView.setLayoutManager(new LinearLayoutManager(this)); + + final DisplayMetrics displayMetrics = getResources().getDisplayMetrics(); + int spanCount = (int) ((displayMetrics.widthPixels / displayMetrics.density) / getResources().getInteger(R.integer.max_dp_grid_view)); + listView.setLayoutManager( + FEATURE_TOGGLE_GRID_VIEW + ? new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL) + : new LinearLayoutManager(this) + ); } private void refreshLists() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java index 21eece55..0b2bb528 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/ItemAdapter.java @@ -8,14 +8,17 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.ColorInt; +import androidx.annotation.IntRange; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; import java.util.ArrayList; import java.util.List; import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.branding.Branded; +import it.niedermann.owncloud.notes.databinding.ItemNotesListNoteItemGridBinding; import it.niedermann.owncloud.notes.databinding.ItemNotesListNoteItemWithExcerptBinding; import it.niedermann.owncloud.notes.databinding.ItemNotesListSectionItemBinding; @@ -25,10 +28,12 @@ public class ItemAdapter extends RecyclerView.Adapter i private static final String TAG = ItemAdapter.class.getSimpleName(); - private static final int TYPE_SECTION = R.layout.item_notes_list_section_item; - private static final int TYPE_NOTE_WITH_EXCERPT = R.layout.item_notes_list_note_item_with_excerpt; - private static final int TYPE_NOTE_WITHOUT_EXCERPT = R.layout.item_notes_list_note_item_without_excerpt; + public static final int TYPE_SECTION = 0; + public static final int TYPE_NOTE_WITH_EXCERPT = 1; + public static final int TYPE_NOTE_WITHOUT_EXCERPT = 2; + private final NoteClickListener noteClickListener; + private final boolean gridView; private List itemList = new ArrayList<>(); private boolean showCategory = true; private CharSequence searchQuery; @@ -38,8 +43,9 @@ public class ItemAdapter extends RecyclerView.Adapter i @ColorInt private int textColor; - public ItemAdapter(@NonNull T context) { + public ItemAdapter(@NonNull T context, boolean gridView) { this.noteClickListener = context; + this.gridView = gridView; this.mainColor = context.getResources().getColor(R.color.defaultBrand); this.textColor = Color.WHITE; } @@ -76,18 +82,33 @@ public class ItemAdapter extends RecyclerView.Adapter i @NonNull @Override public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - switch (viewType) { - case TYPE_SECTION: { - return new SectionViewHolder(ItemNotesListSectionItemBinding.inflate(LayoutInflater.from(parent.getContext()))); + if (gridView) { + switch (viewType) { + case TYPE_SECTION: { + return new SectionViewHolder(ItemNotesListSectionItemBinding.inflate(LayoutInflater.from(parent.getContext()))); + } + case TYPE_NOTE_WITH_EXCERPT: + case TYPE_NOTE_WITHOUT_EXCERPT: { + return new NoteViewGridHolder(ItemNotesListNoteItemGridBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), noteClickListener); + } + default: { + throw new IllegalArgumentException("Not supported viewType: " + viewType); + } } - case TYPE_NOTE_WITH_EXCERPT: { - return new NoteViewHolderWithExcerpt(ItemNotesListNoteItemWithExcerptBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), noteClickListener); - } - case TYPE_NOTE_WITHOUT_EXCERPT: { - return new NoteViewHolderWithoutExcerpt(inflate(LayoutInflater.from(parent.getContext()), parent, false), noteClickListener); - } - default: { - throw new IllegalArgumentException("Not supported viewType: " + viewType); + } else { + switch (viewType) { + case TYPE_SECTION: { + return new SectionViewHolder(ItemNotesListSectionItemBinding.inflate(LayoutInflater.from(parent.getContext()))); + } + case TYPE_NOTE_WITH_EXCERPT: { + return new NoteViewHolderWithExcerpt(ItemNotesListNoteItemWithExcerptBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), noteClickListener); + } + case TYPE_NOTE_WITHOUT_EXCERPT: { + return new NoteViewHolderWithoutExcerpt(inflate(LayoutInflater.from(parent.getContext()), parent, false), noteClickListener); + } + default: { + throw new IllegalArgumentException("Not supported viewType: " + viewType); + } } } } @@ -99,12 +120,9 @@ public class ItemAdapter extends RecyclerView.Adapter i ((SectionViewHolder) holder).bind((SectionItem) itemList.get(position)); break; } - case TYPE_NOTE_WITH_EXCERPT: { - ((NoteViewHolderWithExcerpt) holder).bind((DBNote) itemList.get(position), showCategory, mainColor, textColor, searchQuery); - break; - } + case TYPE_NOTE_WITH_EXCERPT: case TYPE_NOTE_WITHOUT_EXCERPT: { - ((NoteViewHolderWithoutExcerpt) holder).bind((DBNote) itemList.get(position), showCategory, mainColor, textColor, searchQuery); + ((NoteViewHolder) holder).bind((DBNote) itemList.get(position), showCategory, mainColor, textColor, searchQuery); break; } } @@ -160,6 +178,7 @@ public class ItemAdapter extends RecyclerView.Adapter i return itemList.size(); } + @IntRange(from = 0, to = 2) @Override public int getItemViewType(int position) { Item item = getItem(position); diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java new file mode 100644 index 00000000..d9cd30f1 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewGridHolder.java @@ -0,0 +1,39 @@ +package it.niedermann.owncloud.notes.model; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import it.niedermann.owncloud.notes.databinding.ItemNotesListNoteItemGridBinding; + +public class NoteViewGridHolder extends NoteViewHolder { + @NonNull + private final ItemNotesListNoteItemGridBinding binding; + + public NoteViewGridHolder(@NonNull ItemNotesListNoteItemGridBinding binding, @NonNull NoteClickListener noteClickListener) { + super(binding.getRoot(), noteClickListener); + this.binding = binding; + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + + public void showSwipe(boolean left) { + + } + + public void bind(@NonNull DBNote note, boolean showCategory, int mainColor, int textColor, @Nullable CharSequence searchQuery) { + @NonNull final Context context = itemView.getContext(); + bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); + binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); + bindFavorite(binding.noteFavorite, note.isFavorite()); + bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); + bindExcerpt(context, binding.noteContent, searchQuery, note, mainColor); + } + + public View getNoteSwipeable() { + return null; + } +} \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java index 31155700..6272dcf4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolder.java @@ -52,6 +52,8 @@ public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements return noteClickListener.onNoteLongClick(getAdapterPosition(), v); } + public abstract void bind(@NonNull DBNote note, boolean showCategory, int mainColor, int textColor, @Nullable CharSequence searchQuery); + protected void bindCategory(@NonNull Context context, @NonNull TextView noteCategory, boolean showCategory, @NonNull String category, int mainColor) { final boolean isDarkThemeActive = Notes.isDarkThemeActive(context); noteCategory.setVisibility(showCategory && !category.isEmpty() ? View.VISIBLE : View.GONE); @@ -97,8 +99,10 @@ public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements noteFavorite.setOnClickListener(view -> noteClickListener.onNoteFavoriteClick(getAdapterPosition(), view)); } - protected void bindTitleAndExcerpt(@NonNull Context context, @NonNull TextView noteTitle, @Nullable TextView noteExcerpt, @Nullable CharSequence searchQuery, @NonNull DBNote note, int mainColor) { - if (!TextUtils.isEmpty(searchQuery)) { + protected void bindTitle(@NonNull Context context, @NonNull TextView noteTitle, @Nullable CharSequence searchQuery, @NonNull DBNote note, int mainColor) { + if (TextUtils.isEmpty(searchQuery)) { + noteTitle.setText(note.getTitle()); + } else { @ColorInt final int searchBackground = context.getResources().getColor(R.color.bg_highlighted); @ColorInt final int searchForeground = BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, mainColor); @@ -108,28 +112,34 @@ public abstract class NoteViewHolder extends RecyclerView.ViewHolder implements final Pattern pattern = Pattern.compile("(" + Pattern.quote(searchQuery.toString()) + ")", Pattern.CASE_INSENSITIVE); SpannableString spannableString = new SpannableString(note.getTitle()); Matcher matcher = pattern.matcher(spannableString); + while (matcher.find()) { spannableString.setSpan(new ForegroundColorSpan(searchForeground), matcher.start(), matcher.end(), 0); spannableString.setSpan(new BackgroundColorSpan(searchBackground), matcher.start(), matcher.end(), 0); } - noteTitle.setText(spannableString); + } + } + + protected void bindExcerpt(@NonNull Context context, @NonNull TextView noteExcerpt, @Nullable CharSequence searchQuery, @NonNull DBNote note, int mainColor) { + if (TextUtils.isEmpty(searchQuery)) { + noteExcerpt.setText(note.getExcerpt()); + } else { + @ColorInt final int searchBackground = context.getResources().getColor(R.color.bg_highlighted); + @ColorInt final int searchForeground = BrandingUtil.getSecondaryForegroundColorDependingOnTheme(context, mainColor); + + // The Pattern.quote method will add \Q to the very beginning of the string and \E to the end of the string + // It implies that the string between \Q and \E is a literal string and thus the reserved keyword in such string will be ignored. + // See https://stackoverflow.com/questions/15409296/what-is-the-use-of-pattern-quote-method + final Pattern pattern = Pattern.compile("(" + Pattern.quote(searchQuery.toString()) + ")", Pattern.CASE_INSENSITIVE); + SpannableString spannableString = new SpannableString(note.getExcerpt()); + Matcher matcher = pattern.matcher(spannableString); - spannableString = new SpannableString(note.getExcerpt()); - matcher = pattern.matcher(spannableString); while (matcher.find()) { spannableString.setSpan(new ForegroundColorSpan(searchForeground), matcher.start(), matcher.end(), 0); spannableString.setSpan(new BackgroundColorSpan(searchBackground), matcher.start(), matcher.end(), 0); } - - if (noteExcerpt != null) { - noteExcerpt.setText(spannableString); - } - } else { - noteTitle.setText(note.getTitle()); - if (noteExcerpt != null) { - noteExcerpt.setText(note.getExcerpt()); - } + noteExcerpt.setText(spannableString); } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java index 4d1ee302..4433b982 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithExcerpt.java @@ -32,7 +32,8 @@ public class NoteViewHolderWithExcerpt extends NoteViewHolder { bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); bindFavorite(binding.noteFavorite, note.isFavorite()); - bindTitleAndExcerpt(context, binding.noteTitle, binding.noteExcerpt, searchQuery, note, mainColor); + bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); + bindExcerpt(context, binding.noteExcerpt, searchQuery, note, mainColor); } public View getNoteSwipeable() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java index bd7c7e9d..acacda32 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/NoteViewHolderWithoutExcerpt.java @@ -33,7 +33,7 @@ public class NoteViewHolderWithoutExcerpt extends NoteViewHolder { bindCategory(context, binding.noteCategory, showCategory, note.getCategory(), mainColor); binding.noteStatus.setVisibility(DBStatus.VOID.equals(note.getStatus()) ? View.INVISIBLE : View.VISIBLE); bindFavorite(binding.noteFavorite, note.isFavorite()); - bindTitleAndExcerpt(context, binding.noteTitle, null, searchQuery, note, mainColor); + bindTitle(context, binding.noteTitle, searchQuery, note, mainColor); } public View getNoteSwipeable() { diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java b/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java index 98a273a2..b39f77ad 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/model/SectionViewHolder.java @@ -1,8 +1,7 @@ package it.niedermann.owncloud.notes.model; -import android.view.View; - import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; import it.niedermann.owncloud.notes.databinding.ItemNotesListSectionItemBinding; @@ -16,5 +15,9 @@ public class SectionViewHolder extends RecyclerView.ViewHolder { public void bind(SectionItem item) { binding.sectionTitle.setText(item.getTitle()); + + if (itemView.getLayoutParams() != null && itemView.getLayoutParams() instanceof StaggeredGridLayoutManager.LayoutParams) { + ((StaggeredGridLayoutManager.LayoutParams) itemView.getLayoutParams()).setFullSpan(true); + } } } \ No newline at end of file diff --git a/app/src/main/res/layout/item_notes_list_note_item_grid.xml b/app/src/main/res/layout/item_notes_list_note_item_grid.xml new file mode 100644 index 00000000..694b5dd4 --- /dev/null +++ b/app/src/main/res/layout/item_notes_list_note_item_grid.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml new file mode 100644 index 00000000..76c18a8b --- /dev/null +++ b/app/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 150 + \ No newline at end of file