#378 Enhance category handling

This commit is contained in:
stefan-niedermann 2020-01-24 18:19:24 +01:00
parent 97c622d255
commit d12eab5b51
8 changed files with 190 additions and 135 deletions

View file

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

View file

@ -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<RecyclerView.ViewHolder> {
@NonNull
private List<NavigationAdapter.NavigationItem> 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<NavigationAdapter.NavigationItem> categories) {
this.categories = categories;
notifyDataSetChanged();
}
}

View file

@ -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<Void, Void, List<String>> {
private class LoadCategoriesTask extends AsyncTask<String, Void, List<NavigationAdapter.NavigationItem>> {
@Override
protected List<String> doInBackground(Void... voids) {
protected List<NavigationAdapter.NavigationItem> doInBackground(String... searchText) {
NoteSQLiteOpenHelper db = NoteSQLiteOpenHelper.getInstance(getActivity());
List<NavigationAdapter.NavigationItem> items = db.getCategories(accountId);
List<String> 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<String> categories) {
protected void onPostExecute(List<NavigationAdapter.NavigationItem> categories) {
adapter.setCategoryList(categories);
//TODO show creation entry
// if (textCategory.getText().length() == 0) {
// textCategory.showFullDropDown();
// } else {
// textCategory.dismissDropDown();
// }
}
}
}

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

@ -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<DBNote> 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<NavigationAdapter.NavigationItem> 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<NavigationAdapter.NavigationItem> 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);

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/editCategoryLayout"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/editCategoryLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@ -8,11 +10,16 @@
<EditText
android:id="@+id/search"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:scrollbars="vertical" />
android:layout_height="wrap_content"
android:importantForAutofill="no" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:selectableItemBackground"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/item_category" />
</LinearLayout>

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

@ -0,0 +1,11 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp"
android:ellipsize="middle"
android:singleLine="true"
android:textAppearance="@style/NavigationItem"
android:textColor="@color/fg_default"
tools:hint="Username" />