#378 Enhance category handling

This commit is contained in:
stefan-niedermann 2020-01-24 20:49:31 +01:00
parent b446e17aca
commit 0f8e517a7f
11 changed files with 176 additions and 133 deletions

View file

@ -252,7 +252,9 @@ public class EditNoteActivity extends AppCompatActivity implements BaseNoteFragm
ActionBar actionBar = Objects.requireNonNull(getSupportActionBar()); ActionBar actionBar = Objects.requireNonNull(getSupportActionBar());
if (note != null) { if (note != null) {
actionBar.setTitle(note.getTitle()); actionBar.setTitle(note.getTitle());
if (!note.getCategory().isEmpty()) { if (note.getCategory().isEmpty()) {
actionBar.setSubtitle(null);
} else {
actionBar.setSubtitle(NoteUtil.extendCategory(note.getCategory())); actionBar.setSubtitle(NoteUtil.extendCategory(note.getCategory()));
} }
} else { } else {

View file

@ -1,11 +1,13 @@
package it.niedermann.owncloud.notes.android.fragment; package it.niedermann.owncloud.notes.android.fragment;
import android.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; import java.util.ArrayList;
@ -14,19 +16,22 @@ import java.util.List;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R; import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.android.fragment.CategoryDialogFragment.CategoryDialogListener; import it.niedermann.owncloud.notes.model.NavigationAdapter.NavigationItem;
import it.niedermann.owncloud.notes.model.NavigationAdapter;
import it.niedermann.owncloud.notes.util.NoteUtil; import it.niedermann.owncloud.notes.util.NoteUtil;
public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private static final String clearItemId = "clear_item";
private static final String addItemId = "add_item";
@NonNull @NonNull
private List<NavigationAdapter.NavigationItem> categories = new ArrayList<>(); private List<NavigationItem> categories = new ArrayList<>();
@NonNull @NonNull
private CategoryDialogListener categoryListener; private CategoryListener listener;
private Context context;
CategoryAdapter(@NonNull CategoryDialogListener categoryListener) { CategoryAdapter(@NonNull Context context, @NonNull CategoryListener categoryListener) {
this.categoryListener = categoryListener; this.context = context;
this.listener = categoryListener;
} }
@NonNull @NonNull
@ -38,10 +43,30 @@ public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
NavigationAdapter.NavigationItem category = categories.get(position); NavigationItem category = categories.get(position);
CategoryViewHolder categoryViewHolder = (CategoryViewHolder) holder; CategoryViewHolder categoryViewHolder = (CategoryViewHolder) holder;
categoryViewHolder.category.setOnClickListener((v) -> categoryListener.onCategoryChosen(NoteUtil.extendCategory(category.label))); categoryViewHolder.categoryWrapper.setOnClickListener((v) -> {
categoryViewHolder.category.setText(category.label); switch (category.id) {
case addItemId: {
listener.onCategoryAdded();
break;
}
case clearItemId: {
listener.onCategoryCleared();
break;
}
default: {
listener.onCategoryChosen(category.label);
}
}
});
categoryViewHolder.category.setText(NoteUtil.extendCategory(category.label));
if (category.count > 0) {
categoryViewHolder.count.setText(String.valueOf(category.count));
} else {
categoryViewHolder.count.setVisibility(View.GONE);
}
categoryViewHolder.icon.setImageDrawable(context.getResources().getDrawable(category.icon));
} }
@Override @Override
@ -50,17 +75,49 @@ public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolde
} }
static class CategoryViewHolder extends RecyclerView.ViewHolder { static class CategoryViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.categoryWrapper)
View categoryWrapper;
@BindView(R.id.icon)
AppCompatImageView icon;
@BindView(R.id.category) @BindView(R.id.category)
TextView category; TextView category;
@BindView(R.id.count)
TextView count;
private CategoryViewHolder(View view) { private CategoryViewHolder(View view) {
super(view); super(view);
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
} }
} }
void setCategoryList(List<NavigationAdapter.NavigationItem> categories) { void setCategoryList(List<NavigationItem> categories, String currentSearchString) {
this.categories = categories; this.categories = categories;
NavigationItem clearItem = new NavigationItem(clearItemId, context.getString(R.string.no_category), 0, R.drawable.ic_clear_grey_24dp);
this.categories.add(0, clearItem);
if (currentSearchString != null && currentSearchString.trim().length() > 0) {
boolean currentSearchStringIsInCategories = false;
for (NavigationItem category : categories) {
if (currentSearchString.equals(category.label)) {
currentSearchStringIsInCategories = true;
break;
}
}
if (!currentSearchStringIsInCategories) {
NavigationItem addItem = new NavigationItem(addItemId, context.getString(R.string.add_category, currentSearchString.trim()), 0, R.drawable.ic_add_blue_24dp);
this.categories.add(addItem);
}
}
notifyDataSetChanged(); notifyDataSetChanged();
} }
public interface CategoryListener {
void onCategoryChosen(String category);
void onCategoryAdded();
void onCategoryCleared();
}
} }

View file

@ -15,6 +15,7 @@ import android.widget.EditText;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatDialogFragment; import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.List; import java.util.List;
@ -36,6 +37,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
private static final String TAG = CategoryDialogFragment.class.getSimpleName(); private static final String TAG = CategoryDialogFragment.class.getSimpleName();
private static final String STATE_CATEGORY = "category"; private static final String STATE_CATEGORY = "category";
private NoteSQLiteOpenHelper db;
private CategoryDialogListener listener; private CategoryDialogListener listener;
/** /**
@ -79,6 +81,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
} else { } else {
throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getCanonicalName()); throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getCanonicalName());
} }
db = NoteSQLiteOpenHelper.getInstance(getActivity());
} }
@NonNull @NonNull
@ -95,9 +98,24 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
editCategory.setText(savedInstanceState.getString(STATE_CATEGORY)); editCategory.setText(savedInstanceState.getString(STATE_CATEGORY));
} }
adapter = new CategoryAdapter((String category) -> { adapter = new CategoryAdapter(Objects.requireNonNull(getContext()), new CategoryAdapter.CategoryListener() {
@Override
public void onCategoryChosen(String category) {
listener.onCategoryChosen(category); listener.onCategoryChosen(category);
dismiss(); dismiss();
}
@Override
public void onCategoryAdded() {
listener.onCategoryChosen(editCategory.getText().toString());
dismiss();
}
@Override
public void onCategoryCleared() {
listener.onCategoryChosen("");
dismiss();
}
}); });
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
@ -110,12 +128,12 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
@Override @Override
public void onTextChanged(CharSequence s, int start, int before, int count) { public void onTextChanged(CharSequence s, int start, int before, int count) {
new LoadCategoriesTask().execute(editCategory.getText().toString()); // Nothing to do here...
} }
@Override @Override
public void afterTextChanged(Editable s) { public void afterTextChanged(Editable s) {
// Nothing to do here.... new LoadCategoriesTask().execute(editCategory.getText().toString());
} }
}); });
@ -137,6 +155,7 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
@Override @Override
public void onActivityCreated(Bundle savedInstanceState) { public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState); super.onActivityCreated(savedInstanceState);
editCategory.requestFocus();
if (getDialog().getWindow() != null) { if (getDialog().getWindow() != null) {
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
} else { } else {
@ -146,17 +165,17 @@ public class CategoryDialogFragment extends AppCompatDialogFragment {
private class LoadCategoriesTask extends AsyncTask<String, Void, List<NavigationAdapter.NavigationItem>> { private class LoadCategoriesTask extends AsyncTask<String, Void, List<NavigationAdapter.NavigationItem>> {
String currentSearchString;
@Override @Override
protected List<NavigationAdapter.NavigationItem> doInBackground(String... searchText) { protected List<NavigationAdapter.NavigationItem> doInBackground(String... searchText) {
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity()); currentSearchString = searchText[0];
return (searchText[0] == null || searchText[0].length() == 0) return db.searchCategories(accountId, currentSearchString);
? db.getCategories(accountId)
: db.searchCategories(accountId, searchText[0]);
} }
@Override @Override
protected void onPostExecute(List<NavigationAdapter.NavigationItem> categories) { protected void onPostExecute(List<NavigationAdapter.NavigationItem> categories) {
adapter.setCategoryList(categories); adapter.setCategoryList(categories, currentSearchString);
} }
} }
} }

View file

@ -1,71 +0,0 @@
package it.niedermann.owncloud.notes.model;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
import it.niedermann.owncloud.notes.R;
public class CategoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<String> categoryList;
public CategoryAdapter() {
this.categoryList = new ArrayList<>();
}
/**
* Updates the item list and notifies respective view to update.
*
* @param categoryList List of items to be set
*/
public void setCategoryList(@NonNull List<String> categoryList) {
this.categoryList = categoryList;
notifyDataSetChanged();
}
/**
* Adds the given note to the top of the list.
*
* @param category Category that should be added.
*/
public void add(@NonNull String category) {
categoryList.add(0, category);
notifyItemInserted(0);
notifyItemChanged(0);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new CategoryViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.dialog_change_category_single, parent, false));
}
@Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
((CategoryViewHolder) holder).title.setText(categoryList.get(position));
}
@Override
public int getItemCount() {
return categoryList.size();
}
static class CategoryViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.title)
TextView title;
CategoryViewHolder(View view) {
super(view);
ButterKnife.bind(this, view);
}
}
}

View file

@ -635,8 +635,8 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper {
Cursor cursor = db.query( Cursor cursor = db.query(
table_notes, table_notes,
new String[]{key_category, "COUNT(*)"}, new String[]{key_category, "COUNT(*)"},
key_status + " != ? AND " + key_account_id + " = ? AND " + key_category + " LIKE ?", key_status + " != ? AND " + key_account_id + " = ? AND " + key_category + " LIKE ? AND " + key_category + " != \"\"",
new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId, "%" + search + "%"}, new String[]{DBStatus.LOCAL_DELETED.getTitle(), String.valueOf(accountId), "%" + (search == null ? search : search.trim()) + "%"},
key_category, key_category,
null, null,
key_category); key_category);

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#666666" 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="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>

View file

@ -1,24 +1,29 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/editCategoryLayout" android:id="@+id/editCategoryLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
android:padding="?dialogPreferredPadding"> android:padding="?dialogPreferredPadding">
<com.google.android.material.textfield.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText <EditText
android:id="@+id/search" android:id="@+id/search"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:importantForAutofill="no" /> android:importantForAutofill="no"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:scrollbars="vertical" android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_category" /> tools:listitem="@layout/item_category" />

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:padding="36dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Category" />
</LinearLayout>

View file

@ -107,13 +107,15 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/navigationList" android:id="@+id/navigationList"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
app:layoutManager="LinearLayoutManager" /> app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_navigation" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/navigationMenu" android:id="@+id/navigationMenu"
@ -121,7 +123,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
app:layoutManager="LinearLayoutManager" /> app:layoutManager="LinearLayoutManager"
tools:listitem="@layout/item_navigation" />
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout

View file

@ -1,11 +1,43 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/category" android:id="@+id/categoryWrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:padding="16dp" android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="16dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:contentDescription="@null"
android:focusable="false"
android:scaleType="center"
app:srcCompat="@drawable/ic_folder_grey600_24dp" />
<TextView
android:id="@+id/category"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|center_horizontal"
android:layout_weight="1"
android:ellipsize="middle" android:ellipsize="middle"
android:singleLine="true" android:singleLine="true"
android:textAppearance="@style/NavigationItem"
android:textColor="@color/fg_default" android:textColor="@color/fg_default"
tools:hint="Username" /> tools:hint="@string/menu_change_category" />
<TextView
android:id="@+id/count"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:textColor="@color/fg_default"
tools:text="23" />
</LinearLayout>

View file

@ -144,6 +144,8 @@
<string name="bulk_notes_deleted">Deleted %1$d notes</string> <string name="bulk_notes_deleted">Deleted %1$d notes</string>
<string name="bulk_notes_restored">Restored %1$d notes</string> <string name="bulk_notes_restored">Restored %1$d notes</string>
<string name="category_readonly">Read only</string> <string name="category_readonly">Read only</string>
<string name="no_category">No category</string>
<string name="add_category">Add %1$s</string>
<!-- Array: note modes --> <!-- Array: note modes -->
<string-array name="noteMode_entries"> <string-array name="noteMode_entries">