From d12eab5b51d572df2cfed7badc931286e9c5a676 Mon Sep 17 00:00:00 2001 From: stefan-niedermann Date: Fri, 24 Jan 2020 18:19:24 +0100 Subject: [PATCH] #378 Enhance category handling --- .../AccountChooserDialogFragment.java | 4 +- .../android/fragment/CategoryAdapter.java | 66 +++++++++++++ .../fragment/CategoryDialogFragment.java | 99 +++++++++++-------- .../owncloud/notes/model/CategoryAdapter.java | 71 ------------- .../persistence/NoteSQLiteOpenHelper.java | 42 +++++++- .../res/layout/dialog_change_category.xml | 21 ++-- .../layout/dialog_change_category_single.xml | 11 --- app/src/main/res/layout/item_category.xml | 11 +++ 8 files changed, 190 insertions(+), 135 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryAdapter.java delete mode 100644 app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java delete mode 100644 app/src/main/res/layout/dialog_change_category_single.xml create mode 100644 app/src/main/res/layout/item_category.xml diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/AccountChooserDialogFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/AccountChooserDialogFragment.java index b7277d13..69913b1d 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/AccountChooserDialogFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/AccountChooserDialogFragment.java @@ -11,7 +11,7 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; -import androidx.fragment.app.DialogFragment; +import androidx.appcompat.app.AppCompatDialogFragment; import androidx.recyclerview.widget.RecyclerView; import java.util.List; @@ -24,7 +24,7 @@ import it.niedermann.owncloud.notes.android.fragment.AccountChooserAdapter.Accou import it.niedermann.owncloud.notes.model.LocalAccount; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; -public class AccountChooserDialogFragment extends DialogFragment implements AccountChooserListener { +public class AccountChooserDialogFragment extends AppCompatDialogFragment implements AccountChooserListener { private AccountChooserListener accountChooserListener; @BindView(R.id.accounts_list) RecyclerView accountRecyclerView; diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryAdapter.java new file mode 100644 index 00000000..7859a4a4 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryAdapter.java @@ -0,0 +1,66 @@ +package it.niedermann.owncloud.notes.android.fragment; + +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; +import it.niedermann.owncloud.notes.android.fragment.CategoryDialogFragment.CategoryDialogListener; +import it.niedermann.owncloud.notes.model.NavigationAdapter; +import it.niedermann.owncloud.notes.util.NoteUtil; + +public class CategoryAdapter extends RecyclerView.Adapter { + + @NonNull + private List categories = new ArrayList<>(); + @NonNull + private CategoryDialogListener categoryListener; + + CategoryAdapter(@NonNull CategoryDialogListener categoryListener) { + this.categoryListener = categoryListener; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_category, parent, false); + return new CategoryViewHolder(v); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + NavigationAdapter.NavigationItem category = categories.get(position); + CategoryViewHolder categoryViewHolder = (CategoryViewHolder) holder; + categoryViewHolder.category.setOnClickListener((v) -> categoryListener.onCategoryChosen(NoteUtil.extendCategory(category.label))); + categoryViewHolder.category.setText(category.label); + } + + @Override + public int getItemCount() { + return categories.size(); + } + + static class CategoryViewHolder extends RecyclerView.ViewHolder { + @BindView(R.id.category) + TextView category; + + private CategoryViewHolder(View view) { + super(view); + ButterKnife.bind(this, view); + } + } + + void setCategoryList(List categories) { + this.categories = categories; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java index 6c0298b2..9110f056 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/android/fragment/CategoryDialogFragment.java @@ -6,22 +6,24 @@ import android.app.DialogFragment; import android.content.Context; import android.os.AsyncTask; import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.EditText; import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatDialogFragment; +import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import butterknife.BindView; import butterknife.ButterKnife; import it.niedermann.owncloud.notes.R; -import it.niedermann.owncloud.notes.model.CategoryAdapter; import it.niedermann.owncloud.notes.model.NavigationAdapter; import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; @@ -30,9 +32,12 @@ import it.niedermann.owncloud.notes.persistence.NoteSQLiteOpenHelper; * It targetFragment is set it must implement the interface {@link CategoryDialogListener}. * The calling Activity must implement the interface {@link CategoryDialogListener}. */ -public class CategoryDialogFragment extends DialogFragment { +public class CategoryDialogFragment extends AppCompatDialogFragment { private static final String TAG = CategoryDialogFragment.class.getSimpleName(); + private static final String STATE_CATEGORY = "category"; + + private CategoryDialogListener listener; /** * Interface that must be implemented by the calling Activity. @@ -53,6 +58,7 @@ public class CategoryDialogFragment extends DialogFragment { @BindView(R.id.search) EditText editCategory; + @BindView(R.id.recycler_view) RecyclerView recyclerView; @@ -61,11 +67,19 @@ public class CategoryDialogFragment extends DialogFragment { @Override public void onAttach(@NonNull Context context) { super.onAttach(context); - if(getArguments() != null && getArguments().containsKey(PARAM_ACCOUNT_ID)) { + if (getArguments() != null && getArguments().containsKey(PARAM_ACCOUNT_ID)) { accountId = getArguments().getLong(PARAM_ACCOUNT_ID); } else { throw new IllegalArgumentException("Provide at least \"" + PARAM_ACCOUNT_ID + "\""); } + Fragment target = getTargetFragment(); + if (target instanceof CategoryDialogListener) { + listener = (CategoryDialogListener) target; + } else if (getActivity() instanceof CategoryDialogListener) { + listener = (CategoryDialogListener) getActivity(); + } else { + throw new IllegalArgumentException("Calling activity or target fragment must implement " + CategoryDialogListener.class.getCanonicalName()); + } } @NonNull @@ -73,36 +87,54 @@ public class CategoryDialogFragment extends DialogFragment { public Dialog onCreateDialog(Bundle savedInstanceState) { View dialogView = Objects.requireNonNull(getActivity()).getLayoutInflater().inflate(R.layout.dialog_change_category, null); ButterKnife.bind(this, dialogView); + if (savedInstanceState == null) { - if(getArguments() != null && getArguments().containsKey(PARAM_CATEGORY)) { + if (getArguments() != null && getArguments().containsKey(PARAM_CATEGORY)) { editCategory.setText(getArguments().getString(PARAM_CATEGORY)); } + } else if (savedInstanceState.containsKey(STATE_CATEGORY)) { + editCategory.setText(savedInstanceState.getString(STATE_CATEGORY)); } - adapter = new CategoryAdapter(); + + adapter = new CategoryAdapter((String category) -> { + listener.onCategoryChosen(category); + dismiss(); + }); + recyclerView.setAdapter(adapter); - new LoadCategoriesTask().execute(); + new LoadCategoriesTask().execute(""); + editCategory.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + // Nothing to do here... + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + new LoadCategoriesTask().execute(editCategory.getText().toString()); + } + + @Override + public void afterTextChanged(Editable s) { + // Nothing to do here.... + } + }); + return new AlertDialog.Builder(getActivity(), R.style.ncAlertDialog) .setTitle(R.string.change_category_title) .setView(dialogView) .setCancelable(true) - -// @Override -// public void onClick(DialogInterface dialog, int which) { -// CategoryDialogListener listener; -// Fragment target = getTargetFragment(); -// if (target instanceof CategoryDialogListener) { -// listener = (CategoryDialogListener) target; -// } else { -// listener = (CategoryDialogListener) getActivity(); -// } -//// listener.onCategoryChosen(textCategory.getText().toString()); -// } - .setNegativeButton(R.string.simple_cancel, (dialog, which) -> { - // do nothing - }) + .setPositiveButton(R.string.action_edit_save, (dialog, which) -> listener.onCategoryChosen(editCategory.getText().toString())) + .setNegativeButton(R.string.simple_cancel, null) .create(); } + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(STATE_CATEGORY, editCategory.getText().toString()); + } + @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); @@ -114,29 +146,18 @@ public class CategoryDialogFragment extends DialogFragment { } - private class LoadCategoriesTask extends AsyncTask> { + private class LoadCategoriesTask extends AsyncTask> { @Override - protected List doInBackground(Void... voids) { + protected List doInBackground(String... searchText) { NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity()); - List items = db.getCategories(accountId); - List categories = new ArrayList<>(); - for (NavigationAdapter.NavigationItem item : items) { - if (!item.label.isEmpty()) { - categories.add(item.label); - } - } - return categories; + return (searchText[0] == null || searchText[0].length() == 0) + ? db.getCategories(accountId) + : db.searchCategories(accountId, searchText[0]); } @Override - protected void onPostExecute(List categories) { + protected void onPostExecute(List categories) { adapter.setCategoryList(categories); -//TODO show creation entry -// if (textCategory.getText().length() == 0) { -// textCategory.showFullDropDown(); -// } else { -// textCategory.dismissDropDown(); -// } } } } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java b/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java deleted file mode 100644 index a8209153..00000000 --- a/app/src/main/java/it/niedermann/owncloud/notes/model/CategoryAdapter.java +++ /dev/null @@ -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 { - - private List 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 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); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java index 321f1b91..6435bb7e 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/NoteSQLiteOpenHelper.java @@ -81,7 +81,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { private static final String key_etag = "ETAG"; private static final String[] columnsWithoutContent = {key_id, key_remote_id, key_status, key_title, key_modified, key_favorite, key_category, key_etag, key_excerpt}; - private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_favorite, key_category, key_etag, key_excerpt, key_content}; + private static final String[] columns = {key_id, key_remote_id, key_status, key_title, key_modified, key_favorite, key_category, key_etag, key_excerpt, key_content}; private static final String default_order = key_favorite + " DESC, " + key_modified + " DESC"; private static NoteSQLiteOpenHelper instance; @@ -390,9 +390,9 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { Cursor cursor = getReadableDatabase() .query( table_notes, - new String[]{ key_remote_id }, + new String[]{key_remote_id}, key_status + " != ? AND " + key_account_id + " = ?", - new String[]{ DBStatus.LOCAL_DELETED.getTitle(), "" + accountId }, + new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, null, null, null @@ -413,7 +413,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { */ public long getLocalIdByRemoteId(long accountId, long remoteId) { List notes = getNotesCustom(accountId, key_remote_id + " = ? AND " + key_status + " != ? AND " + key_account_id + " = ? ", new String[]{String.valueOf(remoteId), DBStatus.LOCAL_DELETED.getTitle(), "" + accountId}, null, true); - if(notes.isEmpty() || notes.get(0) == null) { + if (notes.isEmpty() || notes.get(0) == null) { throw new IllegalArgumentException("There is no note with remoteId \"" + remoteId + "\""); } return notes.get(0).getId(); @@ -452,7 +452,7 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { /** * Creates a DBNote object from the current row of a Cursor. * - * @param cursor database cursor + * @param cursor database cursor * @param pruneContent whether or not the content should be pruned for performance reasons * @return DBNote */ @@ -626,6 +626,38 @@ public class NoteSQLiteOpenHelper extends SQLiteOpenHelper { return categories; } + // TODO merge with getCategories(long accountId) + @NonNull + @WorkerThread + public List searchCategories(long accountId, String search) { + validateAccountId(accountId); + SQLiteDatabase db = getReadableDatabase(); + Cursor cursor = db.query( + table_notes, + new String[]{key_category, "COUNT(*)"}, + key_status + " != ? AND " + key_account_id + " = ? AND " + key_category + " LIKE ?", + new String[]{DBStatus.LOCAL_DELETED.getTitle(), "" + accountId, "%" + search + "%"}, + key_category, + null, + key_category); + List categories = new ArrayList<>(cursor.getCount()); + while (cursor.moveToNext()) { + Resources res = context.getResources(); + String category = cursor.getString(0).toLowerCase(); + int icon = NavigationAdapter.ICON_FOLDER; + if (category.equals(res.getString(R.string.category_music).toLowerCase())) { + icon = R.drawable.ic_library_music_grey600_24dp; + } else if (category.equals(res.getString(R.string.category_movies).toLowerCase()) || category.equals(res.getString(R.string.category_movie).toLowerCase())) { + icon = R.drawable.ic_local_movies_grey600_24dp; + } else if (category.equals(res.getString(R.string.category_work).toLowerCase())) { + icon = R.drawable.ic_work_grey600_24dp; + } + categories.add(new NavigationAdapter.NavigationItem("category:" + cursor.getString(0), cursor.getString(0), cursor.getInt(1), icon)); + } + cursor.close(); + return categories; + } + public void toggleFavorite(@NonNull DBNote note, @Nullable ICallback callback) { note.setFavorite(!note.isFavorite()); note.setStatus(DBStatus.LOCAL_EDITED); diff --git a/app/src/main/res/layout/dialog_change_category.xml b/app/src/main/res/layout/dialog_change_category.xml index f4d08758..0a66b0c3 100644 --- a/app/src/main/res/layout/dialog_change_category.xml +++ b/app/src/main/res/layout/dialog_change_category.xml @@ -1,6 +1,8 @@ - - + android:layout_height="wrap_content" + android:importantForAutofill="no" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_change_category_single.xml b/app/src/main/res/layout/dialog_change_category_single.xml deleted file mode 100644 index 9a845a99..00000000 --- a/app/src/main/res/layout/dialog_change_category_single.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml new file mode 100644 index 00000000..a4a319c3 --- /dev/null +++ b/app/src/main/res/layout/item_category.xml @@ -0,0 +1,11 @@ +