#831 Migrate from SQLiteOpenHelper to Room

Use LiveData to fetch categories
This commit is contained in:
Stefan Niedermann 2020-10-09 10:38:55 +02:00
parent 2796da4b1e
commit 9f5973afbf
5 changed files with 160 additions and 142 deletions

View file

@ -147,10 +147,8 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
private RecyclerView listView;
protected NotesDatabase db = null;
private NavigationAdapter adapterCategories;
private NavigationItem itemRecent;
private NavigationItem itemFavorites;
private NavigationItem itemUncategorized;
@NonNull
private NavigationCategory navigationSelection = new NavigationCategory(RECENT);
private String navigationOpen = "";
@ -177,6 +175,9 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
// }
};
private LiveData<List<NavigationItem>> navigationItemLiveData;
private Observer<List<NavigationItem>> navigationItemObserver = navigationItems -> this.adapterCategories.setItems(navigationItems);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -230,6 +231,13 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
noteWithCategoryLiveData = mainViewModel.getNotesListLiveData();
noteWithCategoryLiveData.observe(this, noteWithCategoryObserver);
});
mainViewModel.getCurrentAccount().observe(this, (a) -> {
if (navigationItemLiveData != null) {
navigationItemLiveData.removeObserver(navigationItemObserver);
}
navigationItemLiveData = mainViewModel.getNavigationCategories(navigationOpen);
navigationItemLiveData.observe(this, navigationItemObserver);
});
String categoryAdapterSelectedItem = ADAPTER_KEY_RECENT;
if (savedInstanceState == null) {
@ -471,15 +479,13 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
method = CategorySortingMethod.SORT_LEXICOGRAPHICAL_ASC;
}
db.modifyCategoryOrder(localAccount.getId(), navigationSelection, method);
mainViewModel.postFilterChanged();
mainViewModel.postSortOrderOfSpecialNavigationCategoryChanged();
refreshLists();
updateSortMethodIcon(localAccount.getId());
});
}
private void setupNavigationList(final String selectedItem) {
itemRecent = new NavigationItem(ADAPTER_KEY_RECENT, getString(R.string.label_all_notes), null, R.drawable.ic_access_time_grey600_24dp);
itemFavorites = new NavigationItem(ADAPTER_KEY_STARRED, getString(R.string.label_favorites), null, R.drawable.ic_star_yellow_24dp);
adapterCategories = new NavigationAdapter(this, new NavigationAdapter.ClickListener() {
@Override
public void onItemClick(NavigationItem item) {
@ -489,12 +495,24 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
private void selectItem(NavigationItem item, boolean closeNavigation) {
adapterCategories.setSelectedItem(item.id);
// update current selection
if (itemRecent.equals(item)) {
mainViewModel.postSelectedCategory(new NavigationCategory(RECENT));
} else if (itemFavorites.equals(item)) {
mainViewModel.postSelectedCategory(new NavigationCategory(FAVORITES));
} else if (itemUncategorized != null && itemUncategorized.equals(item)) {
mainViewModel.postSelectedCategory(new NavigationCategory(UNCATEGORIZED));
if(item.type != null) {
switch(item.type) {
case RECENT: {
mainViewModel.postSelectedCategory(new NavigationCategory(RECENT));
break;
}
case FAVORITES: {
mainViewModel.postSelectedCategory(new NavigationCategory(FAVORITES));
break;
}
case UNCATEGORIZED: {
mainViewModel.postSelectedCategory(new NavigationCategory(UNCATEGORIZED));
break;
}
default: {
mainViewModel.postSelectedCategory(new NavigationCategory(db.getCategoryDao().getCategoryByTitle(localAccount.getId(), item.label)));
}
}
} else {
mainViewModel.postSelectedCategory(new NavigationCategory(db.getCategoryDao().getCategoryByTitle(localAccount.getId(), item.label)));
}
@ -572,88 +590,6 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
}
}
private class LoadCategoryListTask extends AsyncTask<Void, Void, List<NavigationItem>> {
@Override
protected List<NavigationItem> doInBackground(Void... voids) {
if (localAccount == null) {
return new ArrayList<>();
}
List<CategoryNavigationItem> categories = convertToCategoryNavigationItem(MainActivity.this, db.getCategoryDao().getCategories(localAccount.getId()));
if (!categories.isEmpty() && categories.get(0).label.isEmpty()) {
itemUncategorized = categories.get(0);
itemUncategorized.label = getString(R.string.action_uncategorized);
itemUncategorized.icon = NavigationAdapter.ICON_NOFOLDER;
} else {
itemUncategorized = null;
}
int numFavorites = db.getNoteDao().getFavoritesCount(localAccount.getId());
int numNonFavorites = db.getNoteDao().getNonFavoritesCount(localAccount.getId());
itemFavorites.count = numFavorites;
itemRecent.count = numFavorites + numNonFavorites;
ArrayList<NavigationItem> items = new ArrayList<>();
items.add(itemRecent);
items.add(itemFavorites);
NavigationItem lastPrimaryCategory = null;
NavigationItem lastSecondaryCategory = null;
for (NavigationItem item : categories) {
int slashIndex = item.label.indexOf('/');
String currentPrimaryCategory = slashIndex < 0 ? item.label : item.label.substring(0, slashIndex);
String currentSecondaryCategory = null;
boolean isCategoryOpen = currentPrimaryCategory.equals(navigationOpen);
if (isCategoryOpen && !currentPrimaryCategory.equals(item.label)) {
String currentCategorySuffix = item.label.substring(navigationOpen.length() + 1);
int subSlashIndex = currentCategorySuffix.indexOf('/');
currentSecondaryCategory = subSlashIndex < 0 ? currentCategorySuffix : currentCategorySuffix.substring(0, subSlashIndex);
}
boolean belongsToLastPrimaryCategory = lastPrimaryCategory != null && currentPrimaryCategory.equals(lastPrimaryCategory.label);
boolean belongsToLastSecondaryCategory = belongsToLastPrimaryCategory && lastSecondaryCategory != null && lastSecondaryCategory.label.equals(currentPrimaryCategory + "/" + currentSecondaryCategory);
if (isCategoryOpen && !belongsToLastPrimaryCategory && currentSecondaryCategory != null) {
lastPrimaryCategory = new NavigationItem("category:" + currentPrimaryCategory, currentPrimaryCategory, 0, NavigationAdapter.ICON_MULTIPLE_OPEN);
items.add(lastPrimaryCategory);
belongsToLastPrimaryCategory = true;
}
if (belongsToLastPrimaryCategory && belongsToLastSecondaryCategory) {
lastSecondaryCategory.count += item.count;
lastSecondaryCategory.icon = NavigationAdapter.ICON_SUB_MULTIPLE;
} else if (belongsToLastPrimaryCategory) {
if (isCategoryOpen) {
item.label = currentPrimaryCategory + "/" + currentSecondaryCategory;
item.id = "category:" + item.label;
item.icon = NavigationAdapter.ICON_SUB_FOLDER;
items.add(item);
lastSecondaryCategory = item;
} else {
lastPrimaryCategory.count += item.count;
lastPrimaryCategory.icon = NavigationAdapter.ICON_MULTIPLE;
lastSecondaryCategory = null;
}
} else {
if (isCategoryOpen) {
item.icon = NavigationAdapter.ICON_MULTIPLE_OPEN;
} else {
item.label = currentPrimaryCategory;
item.id = "category:" + item.label;
}
items.add(item);
lastPrimaryCategory = item;
lastSecondaryCategory = null;
}
}
return items;
}
@Override
protected void onPostExecute(List<NavigationItem> items) {
adapterCategories.setItems(items);
}
}
private void setupNavigationMenu() {
final NavigationItem itemFormattingHelp = new NavigationItem("formattingHelp", getString(R.string.action_formatting_help), null, R.drawable.ic_baseline_help_outline_24);
final NavigationItem itemTrashbin = new NavigationItem("trashbin", getString(R.string.action_trashbin), null, R.drawable.ic_delete_grey600_24dp);
@ -723,45 +659,6 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
adapter.removeAll();
return;
}
// View emptyContentView = binding.activityNotesListView.emptyContentView.getRoot();
// emptyContentView.setVisibility(GONE);
// binding.activityNotesListView.progressCircular.setVisibility(VISIBLE);
// fabCreate.show();
// String subtitle;
// if (navigationSelection.category != null) {
// if (navigationSelection.category.isEmpty()) {
// subtitle = getString(R.string.search_in_category, getString(R.string.action_uncategorized));
// } else {
// subtitle = getString(R.string.search_in_category, NoteUtil.extendCategory(navigationSelection.category));
// }
// } else if (navigationSelection.favorite != null && navigationSelection.favorite) {
// subtitle = getString(R.string.search_in_category, getString(R.string.label_favorites));
// } else {
// subtitle = getString(R.string.search_in_all);
// }
// activityBinding.searchText.setText(subtitle);
// CharSequence query = null;
// if (activityBinding.searchView.getQuery().length() != 0) {
// query = activityBinding.searchView.getQuery();
// }
// NotesLoadedListener callback = (List<Item> notes, boolean showCategory, CharSequence searchQuery) -> {
// adapter.setShowCategory(showCategory);
// adapter.setHighlightSearchQuery(searchQuery);
// adapter.setItemList(notes);
// binding.activityNotesListView.progressCircular.setVisibility(GONE);
// if (notes.size() > 0) {
// emptyContentView.setVisibility(GONE);
// } else {
// emptyContentView.setVisibility(VISIBLE);
// }
// if (scrollToTop) {
// listView.scrollToPosition(0);
// }
// };
// new LoadNotesListTask(localAccount.getId(), getApplicationContext(), callback, navigationSelection, query).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new LoadCategoryListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
updateSortMethodIcon(localAccount.getId());
}

View file

@ -9,22 +9,30 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.main.NavigationAdapter.NavigationItem;
import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Category;
import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory;
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType;
import it.niedermann.owncloud.notes.shared.model.Item;
import it.niedermann.owncloud.notes.shared.model.NavigationCategory;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.lifecycle.Transformations.map;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_RECENT;
import static it.niedermann.owncloud.notes.main.MainActivity.ADAPTER_KEY_STARRED;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByCategory;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByInitials;
import static it.niedermann.owncloud.notes.main.slots.SlotterUtil.fillListByTime;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.FAVORITES;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
import static it.niedermann.owncloud.notes.shared.util.DisplayUtils.convertToCategoryNavigationItem;
public class MainViewModel extends AndroidViewModel {
@ -38,9 +46,11 @@ public class MainViewModel extends AndroidViewModel {
@NonNull
private MutableLiveData<String> searchTerm = new MutableLiveData<>();
@NonNull
private MutableLiveData<Void> filterChanged = new MutableLiveData<>();
private MutableLiveData<Void> sortOrderOfSpecialNavigationCategoryChanged = new MutableLiveData<>();
@NonNull
private MutableLiveData<NavigationCategory> selectedCategory = new MutableLiveData<>(new NavigationCategory(RECENT));
@NonNull
private MutableLiveData<List<NavigationItem>> navigationCategories = new MutableLiveData<>();
public MainViewModel(@NonNull Application application) {
super(application);
@ -59,8 +69,8 @@ public class MainViewModel extends AndroidViewModel {
this.selectedCategory.postValue(selectedCategory);
}
public void postFilterChanged() {
this.filterChanged.postValue(null);
public void postSortOrderOfSpecialNavigationCategoryChanged() {
this.sortOrderOfSpecialNavigationCategoryChanged.postValue(null);
}
public LiveData<Account> getCurrentAccount() {
@ -80,7 +90,7 @@ public class MainViewModel extends AndroidViewModel {
mediatorLiveData.addSource(currentAccount, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(searchTerm, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(selectedCategory, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(filterChanged, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(sortOrderOfSpecialNavigationCategoryChanged, (o) -> mediatorLiveData.postValue(null));
return mediatorLiveData;
}
@ -140,4 +150,79 @@ public class MainViewModel extends AndroidViewModel {
return new MutableLiveData<>();
}
}
@NonNull
public LiveData<List<NavigationItem>> getNavigationCategories(String navigationOpen) {
Account currentAccount = getCurrentAccount().getValue();
NavigationCategory selectedCategory = getSelectedCategory().getValue();
if (currentAccount != null && selectedCategory != null) {
return distinctUntilChanged(
map(db.getCategoryDao().getCategoriesLiveData(currentAccount.getId()), fromDatabase -> {
List<NavigationAdapter.CategoryNavigationItem> categories = convertToCategoryNavigationItem(getApplication(), db.getCategoryDao().getCategories(currentAccount.getId()));
int numFavorites = db.getNoteDao().getFavoritesCount(currentAccount.getId());
int numNonFavorites = db.getNoteDao().getNonFavoritesCount(currentAccount.getId());
NavigationItem itemRecent = new NavigationItem(ADAPTER_KEY_RECENT, getApplication().getString(R.string.label_all_notes), numFavorites + numNonFavorites, R.drawable.ic_access_time_grey600_24dp, RECENT);
NavigationItem itemFavorites = new NavigationItem(ADAPTER_KEY_STARRED, getApplication().getString(R.string.label_favorites), numFavorites, R.drawable.ic_star_yellow_24dp, FAVORITES);
ArrayList<NavigationItem> items = new ArrayList<>(fromDatabase.size() + 3);
items.add(itemRecent);
items.add(itemFavorites);
NavigationItem lastPrimaryCategory = null;
NavigationItem lastSecondaryCategory = null;
for (NavigationItem item : categories) {
int slashIndex = item.label.indexOf('/');
String currentPrimaryCategory = slashIndex < 0 ? item.label : item.label.substring(0, slashIndex);
String currentSecondaryCategory = null;
boolean isCategoryOpen = currentPrimaryCategory.equals(navigationOpen);
if (isCategoryOpen && !currentPrimaryCategory.equals(item.label)) {
String currentCategorySuffix = item.label.substring(navigationOpen.length() + 1);
int subSlashIndex = currentCategorySuffix.indexOf('/');
currentSecondaryCategory = subSlashIndex < 0 ? currentCategorySuffix : currentCategorySuffix.substring(0, subSlashIndex);
}
boolean belongsToLastPrimaryCategory = lastPrimaryCategory != null && currentPrimaryCategory.equals(lastPrimaryCategory.label);
boolean belongsToLastSecondaryCategory = belongsToLastPrimaryCategory && lastSecondaryCategory != null && lastSecondaryCategory.label.equals(currentPrimaryCategory + "/" + currentSecondaryCategory);
if (isCategoryOpen && !belongsToLastPrimaryCategory && currentSecondaryCategory != null) {
lastPrimaryCategory = new NavigationItem("category:" + currentPrimaryCategory, currentPrimaryCategory, 0, NavigationAdapter.ICON_MULTIPLE_OPEN);
items.add(lastPrimaryCategory);
belongsToLastPrimaryCategory = true;
}
if (belongsToLastPrimaryCategory && belongsToLastSecondaryCategory) {
lastSecondaryCategory.count += item.count;
lastSecondaryCategory.icon = NavigationAdapter.ICON_SUB_MULTIPLE;
} else if (belongsToLastPrimaryCategory) {
if (isCategoryOpen) {
item.label = currentPrimaryCategory + "/" + currentSecondaryCategory;
item.id = "category:" + item.label;
item.icon = NavigationAdapter.ICON_SUB_FOLDER;
items.add(item);
lastSecondaryCategory = item;
} else {
lastPrimaryCategory.count += item.count;
lastPrimaryCategory.icon = NavigationAdapter.ICON_MULTIPLE;
lastSecondaryCategory = null;
}
} else {
if (isCategoryOpen) {
item.icon = NavigationAdapter.ICON_MULTIPLE_OPEN;
} else {
item.label = currentPrimaryCategory;
item.id = "category:" + item.label;
}
items.add(item);
lastPrimaryCategory = item;
lastSecondaryCategory = null;
}
}
return items;
})
);
} else {
return new MutableLiveData<>();
}
}
}

View file

@ -1,6 +1,7 @@
package it.niedermann.owncloud.notes.main;
import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -11,6 +12,7 @@ import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView;
@ -20,8 +22,11 @@ import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.databinding.ItemNavigationBinding;
import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.UNCATEGORIZED;
public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.ViewHolder> {
@NonNull
@ -55,10 +60,21 @@ public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.Vi
public int icon;
@Nullable
public Integer count;
@Nullable
public ENavigationCategoryType type;
public NavigationItem(@NonNull String id, @NonNull String label, @Nullable Integer count, @DrawableRes int icon) {
this.id = id;
this.label = label;
this.type = TextUtils.isEmpty(label) ? UNCATEGORIZED : null;
this.count = count;
this.icon = icon;
}
public NavigationItem(@NonNull String id, @NonNull String label, @Nullable Integer count, @DrawableRes int icon, @NonNull ENavigationCategoryType type) {
this.id = id;
this.label = label;
this.type = type;
this.count = count;
this.icon = icon;
}
@ -69,7 +85,7 @@ public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.Vi
public Long categoryId;
public CategoryNavigationItem(@NonNull String id, @NonNull String label, @Nullable Integer count, @DrawableRes int icon, @NonNull Long categoryId) {
super(id, label, count, icon);
super(id, label, count, icon, ENavigationCategoryType.DEFAULT_CATEGORY);
this.categoryId = categoryId;
}
}
@ -105,7 +121,7 @@ public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.Vi
count.setVisibility(item.count == null ? View.GONE : View.VISIBLE);
count.setText(String.valueOf(item.count));
if (item.icon > 0) {
icon.setImageDrawable(DrawableCompat.wrap(icon.getResources().getDrawable(item.icon)));
icon.setImageDrawable(DrawableCompat.wrap(ContextCompat.getDrawable(icon.getContext(), item.icon)));
icon.setVisibility(View.VISIBLE);
} else {
icon.setVisibility(View.GONE);
@ -156,6 +172,14 @@ public class NavigationAdapter extends RecyclerView.Adapter<NavigationAdapter.Vi
}
public void setItems(@NonNull List<NavigationItem> items) {
for(NavigationItem item : items) {
if (TextUtils.isEmpty(item.label)) {
item.label = context.getString(R.string.action_uncategorized);
item.icon = NavigationAdapter.ICON_NOFOLDER;
item.type = UNCATEGORIZED;
break;
}
}
this.items = items;
notifyDataSetChanged();
}

View file

@ -1,5 +1,6 @@
package it.niedermann.owncloud.notes.persistence.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
@ -74,6 +75,15 @@ public interface CategoryDao {
@Query("SELECT CATEGORY.id, CATEGORY.title, COUNT(*) as 'totalNotes' FROM CATEGORY INNER JOIN NOTE ON categoryId = CATEGORY.id WHERE STATUS != 'LOCAL_DELETED' AND CATEGORY.accountId = :accountId GROUP BY CATEGORY.title")
List<CategoryWithNotesCount> getCategories(Long accountId);
/**
* This method return all of the categories with given accountId
*
* @param accountId The user account Id
* @return All of the categories with given accountId
*/
@Query("SELECT CATEGORY.id, CATEGORY.title, COUNT(*) as 'totalNotes' FROM CATEGORY INNER JOIN NOTE ON categoryId = CATEGORY.id WHERE STATUS != 'LOCAL_DELETED' AND CATEGORY.accountId = :accountId GROUP BY CATEGORY.title")
LiveData<List<CategoryWithNotesCount>> getCategoriesLiveData(Long accountId);
@Query("SELECT CATEGORY.id, CATEGORY.title, COUNT(*) as 'totalNotes' FROM CATEGORY INNER JOIN NOTE ON categoryId = CATEGORY.id WHERE STATUS != 'LOCAL_DELETED' AND CATEGORY.accountId = :accountId AND CATEGORY.title LIKE '%' + TRIM(:searchTerm) + '%' GROUP BY CATEGORY.title")
List<CategoryWithNotesCount> searchCategories(Long accountId, String searchTerm);
}

View file

@ -7,6 +7,8 @@ import java.io.Serializable;
import it.niedermann.owncloud.notes.persistence.entity.Category;
import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.DEFAULT_CATEGORY;
public class NavigationCategory implements Serializable {
@NonNull
@ -15,15 +17,15 @@ public class NavigationCategory implements Serializable {
private final Category category;
public NavigationCategory(@NonNull ENavigationCategoryType type) {
if (type == ENavigationCategoryType.DEFAULT_CATEGORY) {
throw new IllegalArgumentException("If you want to provide a " + ENavigationCategoryType.DEFAULT_CATEGORY + ", call the constructor with a " + Category.class.getSimpleName());
if (type == DEFAULT_CATEGORY) {
throw new IllegalArgumentException("If you want to provide a " + DEFAULT_CATEGORY + ", call the constructor with a " + Category.class.getSimpleName());
}
this.type = type;
this.category = null;
}
public NavigationCategory(@NonNull Category category) {
this.type = ENavigationCategoryType.DEFAULT_CATEGORY;
this.type = DEFAULT_CATEGORY;
this.category = category;
}