Implement first version of a GridView

This commit is contained in:
Stefan Niedermann 2020-06-10 10:29:17 +02:00
parent 3d0a1fdc12
commit 00e1b5d6f1
10 changed files with 225 additions and 45 deletions

View file

@ -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);
}

View file

@ -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() {

View file

@ -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<RecyclerView.ViewHolder> 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<Item> itemList = new ArrayList<>();
private boolean showCategory = true;
private CharSequence searchQuery;
@ -38,8 +43,9 @@ public class ItemAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> i
@ColorInt
private int textColor;
public <T extends Context & NoteClickListener> ItemAdapter(@NonNull T context) {
public <T extends Context & NoteClickListener> 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<RecyclerView.ViewHolder> 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<RecyclerView.ViewHolder> 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<RecyclerView.ViewHolder> i
return itemList.size();
}
@IntRange(from = 0, to = 2)
@Override
public int getItemViewType(int position) {
Item item = getItem(position);

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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() {

View file

@ -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() {

View file

@ -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);
}
}
}

View file

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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:layout_margin="@dimen/spacer_1x"
android:background="@drawable/list_item_background_selector"
android:elevation="6dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/noteCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacer_1x"
android:layout_marginLeft="@dimen/spacer_1x"
android:background="@drawable/border"
android:maxLines="1"
android:paddingLeft="@dimen/spacer_1x"
android:paddingTop="1dp"
android:paddingRight="@dimen/spacer_1x"
android:paddingBottom="1dp"
android:singleLine="true"
android:textColor="?android:textColorPrimary"
android:textSize="@dimen/secondary_font_size"
tools:maxLength="15"
tools:text="@tools:sample/lorem/random" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/noteFavorite"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/menu_favorite"
android:padding="@dimen/spacer_2x"
tools:src="@drawable/ic_star_yellow_24dp" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/noteStatus"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginTop="12dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:baseline="14dp"
app:srcCompat="@drawable/ic_sync_blue_18dp" />
</FrameLayout>
</LinearLayout>
<com.yydcdut.markdown.MarkdownTextView
android:id="@+id/noteTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacer_2x"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/fg_default"
android:textIsSelectable="true"
android:theme="@style/textViewStyle"
tools:maxLength="50"
tools:text="@tools:sample/lorem/random" />
<com.yydcdut.markdown.MarkdownTextView
android:id="@+id/noteContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacer_2x"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="@color/fg_default"
android:textIsSelectable="true"
android:theme="@style/textViewStyle"
tools:maxLength="200"
tools:text="@tools:sample/lorem/random" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="max_dp_grid_view">150</integer>
</resources>