#831 Migrate from SQLiteOpenHelper to Room

This commit is contained in:
Stefan Niedermann 2020-10-08 15:25:21 +02:00
parent 0b263beea5
commit 92d0438751
8 changed files with 357 additions and 250 deletions

View file

@ -49,7 +49,6 @@ import com.nextcloud.android.sso.model.SingleSignOnAccount;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import it.niedermann.owncloud.notes.FormattingHelpActivity; import it.niedermann.owncloud.notes.FormattingHelpActivity;
@ -74,12 +73,9 @@ import it.niedermann.owncloud.notes.main.items.list.NotesListViewItemTouchHelper
import it.niedermann.owncloud.notes.main.items.section.SectionItemDecoration; import it.niedermann.owncloud.notes.main.items.section.SectionItemDecoration;
import it.niedermann.owncloud.notes.persistence.CapabilitiesClient; import it.niedermann.owncloud.notes.persistence.CapabilitiesClient;
import it.niedermann.owncloud.notes.persistence.CapabilitiesWorker; import it.niedermann.owncloud.notes.persistence.CapabilitiesWorker;
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask;
import it.niedermann.owncloud.notes.persistence.LoadNotesListTask.NotesLoadedListener;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper; import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper;
import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper.ViewProvider; import it.niedermann.owncloud.notes.persistence.NoteServerSyncHelper.ViewProvider;
import it.niedermann.owncloud.notes.persistence.NotesDatabase; import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.dao.AccountDao;
import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note; import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory;
@ -164,14 +160,11 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
swipeRefreshLayout.setRefreshing(false); swipeRefreshLayout.setRefreshing(false);
}; };
private LiveData<List<NoteWithCategory>> noteWithCategoryLiveData; private LiveData<List<Item>> noteWithCategoryLiveData;
private Observer<List<NoteWithCategory>> noteWithCategoryObserver = notes -> { private Observer<List<Item>> noteWithCategoryObserver = notes -> {
adapter.setShowCategory(mainViewModel.getSelectedCategory().getValue() == null); adapter.setShowCategory(mainViewModel.getSelectedCategory().getValue() == null);
adapter.setHighlightSearchQuery(mainViewModel.getSearchTerm().getValue()); adapter.setHighlightSearchQuery(mainViewModel.getSearchTerm().getValue());
List<Item> items = new ArrayList<>(); adapter.setItemList(notes);
items.addAll(notes);
// yes, NoteWithCatecory are Items, check. BUT: setItemList expects an List<Item(!)>, the Elements in notes ARE Items, but the List-type isn't correct, since you pass the List, not the items!
adapter.setItemList(items);
binding.activityNotesListView.progressCircular.setVisibility(GONE); binding.activityNotesListView.progressCircular.setVisibility(GONE);
binding.activityNotesListView.emptyContentView.getRoot().setVisibility(notes.size() > 0 ? GONE : VISIBLE); binding.activityNotesListView.emptyContentView.getRoot().setVisibility(notes.size() > 0 ? GONE : VISIBLE);
// if (scrollToTop) { // if (scrollToTop) {
@ -196,18 +189,46 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
this.fabCreate = binding.activityNotesListView.fabCreate; this.fabCreate = binding.activityNotesListView.fabCreate;
this.listView = binding.activityNotesListView.recyclerView; this.listView = binding.activityNotesListView.recyclerView;
mainViewModel.getSelectedCategory().observe(this, (category) -> {
View emptyContentView = binding.activityNotesListView.emptyContentView.getRoot();
emptyContentView.setVisibility(GONE);
binding.activityNotesListView.progressCircular.setVisibility(VISIBLE);
this.navigationSelection = category;
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);
});
mainViewModel.filterChanged().observe(this, (v) -> {
if (noteWithCategoryLiveData != null) {
noteWithCategoryLiveData.removeObserver(noteWithCategoryObserver);
}
noteWithCategoryLiveData = mainViewModel.getNotesListLiveData();
noteWithCategoryLiveData.observe(this, noteWithCategoryObserver);
});
String categoryAdapterSelectedItem = ADAPTER_KEY_RECENT; String categoryAdapterSelectedItem = ADAPTER_KEY_RECENT;
if (savedInstanceState == null) { if (savedInstanceState == null) {
if (ACTION_RECENT.equals(getIntent().getAction())) { if (ACTION_RECENT.equals(getIntent().getAction())) {
categoryAdapterSelectedItem = ADAPTER_KEY_RECENT; categoryAdapterSelectedItem = ADAPTER_KEY_RECENT;
} else if (ACTION_FAVORITES.equals(getIntent().getAction())) { } else if (ACTION_FAVORITES.equals(getIntent().getAction())) {
categoryAdapterSelectedItem = ADAPTER_KEY_STARRED; categoryAdapterSelectedItem = ADAPTER_KEY_STARRED;
navigationSelection = new OldCategory(null, true); mainViewModel.postSelectedCategory(new OldCategory(null, true));
} }
} else { } else {
Object savedCategory = savedInstanceState.getSerializable(SAVED_STATE_NAVIGATION_SELECTION); Object savedCategory = savedInstanceState.getSerializable(SAVED_STATE_NAVIGATION_SELECTION);
if (savedCategory != null) { if (savedCategory != null) {
navigationSelection = (OldCategory) savedCategory; mainViewModel.postSelectedCategory((OldCategory) savedCategory);
} }
navigationOpen = savedInstanceState.getString(SAVED_STATE_NAVIGATION_OPEN); navigationOpen = savedInstanceState.getString(SAVED_STATE_NAVIGATION_OPEN);
categoryAdapterSelectedItem = savedInstanceState.getString(SAVED_STATE_NAVIGATION_ADAPTER_SLECTION); categoryAdapterSelectedItem = savedInstanceState.getString(SAVED_STATE_NAVIGATION_ADAPTER_SLECTION);
@ -220,11 +241,6 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
activityBinding.activityNotesListView.setBackgroundColor(ContextCompat.getColor(this, R.color.primary)); activityBinding.activityNotesListView.setBackgroundColor(ContextCompat.getColor(this, R.color.primary));
} }
AccountDao dao = db.getAccountDao();
new Thread(() -> {
List<Account> localAccountEntities = dao.getAccounts();
Log.v("TEST", localAccountEntities.size() + " acs");
}).start();
setupToolbars(); setupToolbars();
setupNavigationList(categoryAdapterSelectedItem); setupNavigationList(categoryAdapterSelectedItem);
setupNavigationMenu(); setupNavigationMenu();
@ -245,6 +261,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
List<Account> localAccounts = db.getAccountDao().getAccounts(); List<Account> localAccounts = db.getAccountDao().getAccounts();
if (localAccounts.size() > 0) { if (localAccounts.size() > 0) {
localAccount = localAccounts.get(0); localAccount = localAccounts.get(0);
mainViewModel.postCurrentAccount(localAccount);
} }
} }
if (!notAuthorizedAccountHandled) { if (!notAuthorizedAccountHandled) {
@ -275,6 +292,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
fabCreate.hide(); fabCreate.hide();
SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName); SingleAccountHelper.setCurrentAccount(getApplicationContext(), accountName);
localAccount = db.getAccountDao().getLocalAccountByAccountName(accountName); localAccount = db.getAccountDao().getLocalAccountByAccountName(accountName);
mainViewModel.postCurrentAccount(localAccount);
if (localAccount != null) { if (localAccount != null) {
try { try {
BrandingUtil.saveBrandColors(this, localAccount.getColor(), localAccount.getTextColor()); BrandingUtil.saveBrandColors(this, localAccount.getColor(), localAccount.getTextColor());
@ -361,7 +379,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
@Override @Override
public boolean onQueryTextChange(String newText) { public boolean onQueryTextChange(String newText) {
refreshLists(); mainViewModel.postSearchTerm(newText);
return true; return true;
} }
}); });
@ -457,13 +475,13 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
adapterCategories.setSelectedItem(item.id); adapterCategories.setSelectedItem(item.id);
// update current selection // update current selection
if (itemRecent.equals(item)) { if (itemRecent.equals(item)) {
navigationSelection = new OldCategory(null, null); mainViewModel.postSelectedCategory(new OldCategory(null, null));
} else if (itemFavorites.equals(item)) { } else if (itemFavorites.equals(item)) {
navigationSelection = new OldCategory(null, true); mainViewModel.postSelectedCategory(new OldCategory(null, true));
} else if (itemUncategorized != null && itemUncategorized.equals(item)) { } else if (itemUncategorized != null && itemUncategorized.equals(item)) {
navigationSelection = new OldCategory("", null); mainViewModel.postSelectedCategory(new OldCategory("", null));
} else { } else {
navigationSelection = new OldCategory(item.label, null); mainViewModel.postSelectedCategory(new OldCategory(item.label, null));
} }
// auto-close sub-folder in Navigation if selection is outside of that folder // auto-close sub-folder in Navigation if selection is outside of that folder
@ -686,43 +704,43 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
adapter.removeAll(); adapter.removeAll();
return; return;
} }
View emptyContentView = binding.activityNotesListView.emptyContentView.getRoot(); // View emptyContentView = binding.activityNotesListView.emptyContentView.getRoot();
emptyContentView.setVisibility(GONE); // emptyContentView.setVisibility(GONE);
binding.activityNotesListView.progressCircular.setVisibility(VISIBLE); // binding.activityNotesListView.progressCircular.setVisibility(VISIBLE);
fabCreate.show(); // fabCreate.show();
String subtitle; // String subtitle;
if (navigationSelection.category != null) { // if (navigationSelection.category != null) {
if (navigationSelection.category.isEmpty()) { // if (navigationSelection.category.isEmpty()) {
subtitle = getString(R.string.search_in_category, getString(R.string.action_uncategorized)); // subtitle = getString(R.string.search_in_category, getString(R.string.action_uncategorized));
} else { // } else {
subtitle = getString(R.string.search_in_category, NoteUtil.extendCategory(navigationSelection.category)); // subtitle = getString(R.string.search_in_category, NoteUtil.extendCategory(navigationSelection.category));
} // }
} else if (navigationSelection.favorite != null && navigationSelection.favorite) { // } else if (navigationSelection.favorite != null && navigationSelection.favorite) {
subtitle = getString(R.string.search_in_category, getString(R.string.label_favorites)); // subtitle = getString(R.string.search_in_category, getString(R.string.label_favorites));
} else { // } else {
subtitle = getString(R.string.search_in_all); // subtitle = getString(R.string.search_in_all);
} // }
activityBinding.searchText.setText(subtitle); // activityBinding.searchText.setText(subtitle);
CharSequence query = null; // CharSequence query = null;
if (activityBinding.searchView.getQuery().length() != 0) { // if (activityBinding.searchView.getQuery().length() != 0) {
query = activityBinding.searchView.getQuery(); // query = activityBinding.searchView.getQuery();
} // }
NotesLoadedListener callback = (List<Item> notes, boolean showCategory, CharSequence searchQuery) -> { // NotesLoadedListener callback = (List<Item> notes, boolean showCategory, CharSequence searchQuery) -> {
adapter.setShowCategory(showCategory); // adapter.setShowCategory(showCategory);
adapter.setHighlightSearchQuery(searchQuery); // adapter.setHighlightSearchQuery(searchQuery);
adapter.setItemList(notes); // adapter.setItemList(notes);
binding.activityNotesListView.progressCircular.setVisibility(GONE); // binding.activityNotesListView.progressCircular.setVisibility(GONE);
if (notes.size() > 0) { // if (notes.size() > 0) {
emptyContentView.setVisibility(GONE); // emptyContentView.setVisibility(GONE);
} else { // } else {
emptyContentView.setVisibility(VISIBLE); // emptyContentView.setVisibility(VISIBLE);
} // }
if (scrollToTop) { // if (scrollToTop) {
listView.scrollToPosition(0); // listView.scrollToPosition(0);
} // }
}; // };
new LoadNotesListTask(localAccount.getId(), getApplicationContext(), callback, navigationSelection, query).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // new LoadNotesListTask(localAccount.getId(), getApplicationContext(), callback, navigationSelection, query).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new LoadCategoryListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new LoadCategoryListTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
updateSortMethodIcon(localAccount.getId()); updateSortMethodIcon(localAccount.getId());
@ -984,6 +1002,7 @@ public class MainActivity extends LockedActivity implements NoteClickListener, V
List<Account> remainingAccounts = db.getAccountDao().getAccounts(); List<Account> remainingAccounts = db.getAccountDao().getAccounts();
if (remainingAccounts.size() > 0) { if (remainingAccounts.size() > 0) {
this.localAccount = remainingAccounts.get(0); this.localAccount = remainingAccounts.get(0);
mainViewModel.postCurrentAccount(localAccount);
selectAccount(this.localAccount.getAccountName()); selectAccount(this.localAccount.getAccountName());
} else { } else {
selectAccount(null); selectAccount(null);

View file

@ -3,20 +3,25 @@ package it.niedermann.owncloud.notes.main;
import android.app.Application; import android.app.Application;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.arch.core.util.Function;
import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData; import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import java.util.List; import java.util.List;
import it.niedermann.owncloud.notes.persistence.NotesDatabase; import it.niedermann.owncloud.notes.persistence.NotesDatabase;
import it.niedermann.owncloud.notes.persistence.entity.Account; import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory;
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
import it.niedermann.owncloud.notes.shared.model.Item; import it.niedermann.owncloud.notes.shared.model.Item;
import it.niedermann.owncloud.notes.shared.model.OldCategory; import it.niedermann.owncloud.notes.shared.model.OldCategory;
import static androidx.lifecycle.Transformations.distinctUntilChanged; 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;
public class MainViewModel extends AndroidViewModel { public class MainViewModel extends AndroidViewModel {
@ -28,9 +33,7 @@ public class MainViewModel extends AndroidViewModel {
@NonNull @NonNull
private MutableLiveData<String> searchTerm = new MutableLiveData<>(); private MutableLiveData<String> searchTerm = new MutableLiveData<>();
@NonNull @NonNull
private MutableLiveData<OldCategory> selectedCategory = new MutableLiveData<>(); private MutableLiveData<OldCategory> selectedCategory = new MutableLiveData<>(new OldCategory(null, null));
private MediatorLiveData<List<Item>> notesListLiveData = new MediatorLiveData<>();
public MainViewModel(@NonNull Application application) { public MainViewModel(@NonNull Application application) {
super(application); super(application);
@ -49,6 +52,10 @@ public class MainViewModel extends AndroidViewModel {
this.selectedCategory.postValue(selectedCategory); this.selectedCategory.postValue(selectedCategory);
} }
public LiveData<Account> getCurrentAccount() {
return currentAccount;
}
public LiveData<String> getSearchTerm() { public LiveData<String> getSearchTerm() {
return searchTerm; return searchTerm;
} }
@ -59,13 +66,44 @@ public class MainViewModel extends AndroidViewModel {
public LiveData<Void> filterChanged() { public LiveData<Void> filterChanged() {
MediatorLiveData<Void> mediatorLiveData = new MediatorLiveData<>(); MediatorLiveData<Void> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(distinctUntilChanged(currentAccount), (o) -> mediatorLiveData.postValue(null)); mediatorLiveData.addSource(currentAccount, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(distinctUntilChanged(searchTerm), (o) -> mediatorLiveData.postValue(null)); mediatorLiveData.addSource(searchTerm, (o) -> mediatorLiveData.postValue(null));
mediatorLiveData.addSource(distinctUntilChanged(selectedCategory), (o) -> mediatorLiveData.postValue(null)); mediatorLiveData.addSource(selectedCategory, (o) -> mediatorLiveData.postValue(null));
return mediatorLiveData; return mediatorLiveData;
} }
public LiveData<List<Item>> getNotesListLiveData() { public LiveData<List<Item>> getNotesListLiveData() {
return notesListLiveData; Account currentAccount = getCurrentAccount().getValue();
OldCategory selectedCategory = getSelectedCategory().getValue();
LiveData<List<NoteWithCategory>> fromDatabase;
if (currentAccount != null && selectedCategory != null) {
Long accountId = currentAccount.getId();
CategorySortingMethod sortingMethod = db.getCategoryOrder(accountId, selectedCategory);
String searchQuery = getSearchTerm().getValue();
searchQuery = searchQuery == null ? "%" : "%" + searchQuery.trim() + "%";
if (Boolean.TRUE.equals(selectedCategory.favorite)) { // Favorites
fromDatabase = db.getNoteDao().searchNotesFavorites(accountId, searchQuery, sortingMethod.getSorder());
} else if (selectedCategory.category != null && selectedCategory.category.length() == 0) { // Uncategorized
fromDatabase = db.getNoteDao().searchNotesUncategorized(accountId, searchQuery, sortingMethod.getSorder());
} else if( selectedCategory.category == null && selectedCategory.favorite == null) { // Recent
fromDatabase = db.getNoteDao().searchNotesAll(accountId, searchQuery, sortingMethod.getSorder());
} else { // A special category
fromDatabase = db.getNoteDao().searchNotesByCategory(accountId, searchQuery, selectedCategory.category, sortingMethod.getSorder());
}
return Transformations.map(fromDatabase, (Function<List<NoteWithCategory>, List<Item>>) noteList -> {
if (selectedCategory.category == null) {
if (sortingMethod == CategorySortingMethod.SORT_MODIFIED_DESC) {
return fillListByTime(getApplication(), noteList);
} else {
return fillListByInitials(getApplication(), noteList);
}
} else {
return fillListByCategory(noteList, selectedCategory.category);
}
});
} else {
return new MutableLiveData<>();
}
} }
} }

View file

@ -0,0 +1,74 @@
package it.niedermann.owncloud.notes.main.slots;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.main.items.section.SectionItem;
import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory;
import it.niedermann.owncloud.notes.shared.model.Item;
import it.niedermann.owncloud.notes.shared.util.NoteUtil;
public class SlotterUtil {
private SlotterUtil() {
// Util class
}
@NonNull
public static List<Item> fillListByCategory(@NonNull List<NoteWithCategory> noteList, @Nullable String currentCategory) {
List<Item> itemList = new ArrayList<>();
for (NoteWithCategory note : noteList) {
if (currentCategory != null && !currentCategory.equals(note.getCategory())) {
itemList.add(new SectionItem(NoteUtil.extendCategory(note.getCategory())));
}
itemList.add(note);
currentCategory = note.getCategory();
}
return itemList;
}
@NonNull
public static List<Item> fillListByTime(@NonNull Context context, @NonNull List<NoteWithCategory> noteList) {
List<Item> itemList = new ArrayList<>();
Timeslotter timeslotter = new Timeslotter(context);
String lastTimeslot = null;
for (int i = 0; i < noteList.size(); i++) {
NoteWithCategory currentNote = noteList.get(i);
String timeslot = timeslotter.getTimeslot(currentNote.getNote());
if (i > 0 && !timeslot.equals(lastTimeslot)) {
itemList.add(new SectionItem(timeslot));
}
itemList.add(currentNote);
lastTimeslot = timeslot;
}
return itemList;
}
@NonNull
public static List<Item> fillListByInitials(@NonNull Context context, @NonNull List<NoteWithCategory> noteList) {
List<Item> itemList = new ArrayList<>();
String lastInitials = null;
for (int i = 0; i < noteList.size(); i++) {
NoteWithCategory currentNote = noteList.get(i);
String initials = currentNote.getNote().getTitle().substring(0, 1).toUpperCase();
if (!initials.matches("[A-Z\\u00C0-\\u00DF]")) {
initials = initials.matches("[\\u0250-\\uFFFF]") ? context.getString(R.string.simple_other) : "#";
}
if (i > 0 && !initials.equals(lastInitials)) {
itemList.add(new SectionItem(initials));
}
itemList.add(currentNote);
lastInitials = initials;
}
return itemList;
}
}

View file

@ -0,0 +1,22 @@
package it.niedermann.owncloud.notes.main.slots;
import java.util.Calendar;
public class Timeslot {
private final String label;
private final Calendar time;
Timeslot(String label, int month, int day) {
this.label = label;
this.time = Calendar.getInstance();
this.time.set(this.time.get(Calendar.YEAR), month, day, 0, 0, 0);
}
public String getLabel() {
return label;
}
public Calendar getTime() {
return time;
}
}

View file

@ -0,0 +1,53 @@
package it.niedermann.owncloud.notes.main.slots;
import android.content.Context;
import android.text.format.DateUtils;
import androidx.annotation.NonNull;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.persistence.entity.Note;
public class Timeslotter {
private final List<Timeslot> timeslots = new ArrayList<>();
private final Calendar lastYear;
private final Context context;
public Timeslotter(@NonNull Context context) {
this.context = context;
Calendar now = Calendar.getInstance();
int month = now.get(Calendar.MONTH);
int day = now.get(Calendar.DAY_OF_MONTH);
int offsetWeekStart = (now.get(Calendar.DAY_OF_WEEK) - now.getFirstDayOfWeek() + 7) % 7;
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_today), month, day));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_yesterday), month, day - 1));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_this_week), month, day - offsetWeekStart));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_last_week), month, day - offsetWeekStart - 7));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_this_month), month, 1));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_last_month), month - 1, 1));
lastYear = Calendar.getInstance();
lastYear.set(now.get(Calendar.YEAR) - 1, 0, 1, 0, 0, 0);
}
public String getTimeslot(Note note) {
if (note.getFavorite()) {
return "";
}
Calendar modified = note.getModified();
for (Timeslot timeslot : timeslots) {
if (!modified.before(timeslot.getTime())) {
return timeslot.getLabel();
}
}
if (!modified.before(this.lastYear)) {
// use YEAR and MONTH in a format based on current locale
return DateUtils.formatDateTime(context, modified.getTimeInMillis(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY);
} else {
return Integer.toString(modified.get(Calendar.YEAR));
}
}
}

View file

@ -1,179 +1,75 @@
package it.niedermann.owncloud.notes.persistence; //package it.niedermann.owncloud.notes.persistence;
//
import android.content.Context; //import android.content.Context;
import android.os.AsyncTask; //import android.os.AsyncTask;
import android.text.TextUtils; //import android.text.TextUtils;
import android.text.format.DateUtils; //import android.text.format.DateUtils;
//
import androidx.annotation.NonNull; //import androidx.annotation.NonNull;
import androidx.annotation.Nullable; //import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread; //import androidx.annotation.WorkerThread;
//
import java.util.ArrayList; //import java.util.ArrayList;
import java.util.Calendar; //import java.util.Calendar;
import java.util.List; //import java.util.List;
//
import it.niedermann.owncloud.notes.R; //import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.main.items.section.SectionItem; //import it.niedermann.owncloud.notes.main.items.section.SectionItem;
import it.niedermann.owncloud.notes.persistence.entity.Note; //import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory; //import it.niedermann.owncloud.notes.persistence.entity.NoteWithCategory;
import it.niedermann.owncloud.notes.shared.model.OldCategory; //import it.niedermann.owncloud.notes.shared.model.OldCategory;
import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod; //import it.niedermann.owncloud.notes.shared.model.CategorySortingMethod;
import it.niedermann.owncloud.notes.shared.model.Item; //import it.niedermann.owncloud.notes.shared.model.Item;
import it.niedermann.owncloud.notes.shared.util.NoteUtil; //import it.niedermann.owncloud.notes.shared.util.NoteUtil;
//
public class LoadNotesListTask extends AsyncTask<Void, Void, List<Item>> { //public class LoadNotesListTask extends AsyncTask<Void, Void, List<Item>> {
//
private final Context context; // private final Context context;
private final NotesLoadedListener callback; // private final NotesLoadedListener callback;
private final OldCategory category; // private final OldCategory category;
private final String searchQuery; // private final String searchQuery;
private final long accountId; // private final long accountId;
//
public LoadNotesListTask(long accountId, @NonNull Context context, @NonNull NotesLoadedListener callback, @NonNull OldCategory category, @Nullable CharSequence searchQuery) { // public LoadNotesListTask(long accountId, @NonNull Context context, @NonNull NotesLoadedListener callback, @NonNull OldCategory category, @Nullable CharSequence searchQuery) {
this.context = context; // this.context = context;
this.callback = callback; // this.callback = callback;
this.category = category; // this.category = category;
this.searchQuery = searchQuery == null ? "%" : "%" + searchQuery + "%"; // this.searchQuery = searchQuery == null ? "%" : "%" + searchQuery + "%";
this.accountId = accountId; // this.accountId = accountId;
} // }
//
@Override // @Override
protected List<Item> doInBackground(Void... voids) { // protected List<Item> doInBackground(Void... voids) {
List<NoteWithCategory> noteList; // List<NoteWithCategory> noteList;
NotesDatabase db = NotesDatabase.getInstance(context); // NotesDatabase db = NotesDatabase.getInstance(context);
CategorySortingMethod sortingMethod = db.getCategoryOrder(accountId, category); // CategorySortingMethod sortingMethod = db.getCategoryOrder(accountId, category);
//
if(Boolean.TRUE.equals(category.favorite)) { // if(Boolean.TRUE.equals(category.favorite)) {
noteList = db.getNoteDao().searchNotesByCategoryFavorites(accountId, searchQuery, sortingMethod); // noteList = db.getNoteDao().searchNotesByCategoryFavoritesDirectly(accountId, searchQuery, sortingMethod);
} else if(TextUtils.isEmpty(category.category)) { // } else if(TextUtils.isEmpty(category.category)) {
noteList = db.getNoteDao().searchNotesByUncategorized(accountId, searchQuery, sortingMethod); // noteList = db.getNoteDao().searchNotesByUncategorizedDirectly(accountId, searchQuery, sortingMethod);
} else { // } else {
noteList = db.getNoteDao().searchNotesByCategory(accountId, searchQuery, category.category, sortingMethod); // noteList = db.getNoteDao().searchNotesByCategoryDirectly(accountId, searchQuery, category.category, sortingMethod);
} // }
//
if (category.category == null) { //// if (category.category == null) {
if (sortingMethod == CategorySortingMethod.SORT_MODIFIED_DESC) { //// if (sortingMethod == CategorySortingMethod.SORT_MODIFIED_DESC) {
return fillListByTime(noteList); //// return fillListByTime(noteList);
} else { //// } else {
return fillListByInitials(noteList); //// return fillListByInitials(noteList);
} //// }
} else { //// } else {
return fillListByCategory(noteList); //// return fillListByCategory(noteList);
} //// }
} // }
//
@NonNull // @Override
@WorkerThread // protected void onPostExecute(List<Item> items) {
private List<Item> fillListByCategory(@NonNull List<NoteWithCategory> noteList) { // callback.onNotesLoaded(items, category.category == null, searchQuery);
List<Item> itemList = new ArrayList<>(); // }
String currentCategory = category.category; //
for (NoteWithCategory note : noteList) { // public interface NotesLoadedListener {
if (currentCategory != null && !currentCategory.equals(note.getCategory())) { // void onNotesLoaded(List<Item> notes, boolean showCategory, CharSequence searchQuery);
itemList.add(new SectionItem(NoteUtil.extendCategory(note.getCategory()))); // }
} // }
//}
itemList.add(note);
currentCategory = note.getCategory();
}
return itemList;
}
@NonNull
@WorkerThread
private List<Item> fillListByTime(@NonNull List<NoteWithCategory> noteList) {
List<Item> itemList = new ArrayList<>();
Timeslotter timeslotter = new Timeslotter();
String lastTimeslot = null;
for (int i = 0; i < noteList.size(); i++) {
NoteWithCategory currentNote = noteList.get(i);
String timeslot = timeslotter.getTimeslot(currentNote.getNote());
if (i > 0 && !timeslot.equals(lastTimeslot)) {
itemList.add(new SectionItem(timeslot));
}
itemList.add(currentNote);
lastTimeslot = timeslot;
}
return itemList;
}
@NonNull
@WorkerThread
private List<Item> fillListByInitials(@NonNull List<NoteWithCategory> noteList) {
List<Item> itemList = new ArrayList<>();
String lastInitials = null;
for (int i = 0; i < noteList.size(); i++) {
NoteWithCategory currentNote = noteList.get(i);
String initials = currentNote.getNote().getTitle().substring(0, 1).toUpperCase();
if (!initials.matches("[A-Z\\u00C0-\\u00DF]")) {
initials = initials.matches("[\\u0250-\\uFFFF]") ? context.getString(R.string.simple_other) : "#";
}
if (i > 0 && !initials.equals(lastInitials)) {
itemList.add(new SectionItem(initials));
}
itemList.add(currentNote);
lastInitials = initials;
}
return itemList;
}
@Override
protected void onPostExecute(List<Item> items) {
callback.onNotesLoaded(items, category.category == null, searchQuery);
}
public interface NotesLoadedListener {
void onNotesLoaded(List<Item> notes, boolean showCategory, CharSequence searchQuery);
}
private class Timeslotter {
private final List<Timeslot> timeslots = new ArrayList<>();
private final Calendar lastYear;
Timeslotter() {
Calendar now = Calendar.getInstance();
int month = now.get(Calendar.MONTH);
int day = now.get(Calendar.DAY_OF_MONTH);
int offsetWeekStart = (now.get(Calendar.DAY_OF_WEEK) - now.getFirstDayOfWeek() + 7) % 7;
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_today), month, day));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_yesterday), month, day - 1));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_this_week), month, day - offsetWeekStart));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_last_week), month, day - offsetWeekStart - 7));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_this_month), month, 1));
timeslots.add(new Timeslot(context.getResources().getString(R.string.listview_updated_last_month), month - 1, 1));
lastYear = Calendar.getInstance();
lastYear.set(now.get(Calendar.YEAR) - 1, 0, 1, 0, 0, 0);
}
private String getTimeslot(Note note) {
if (note.getFavorite()) {
return "";
}
Calendar modified = note.getModified();
for (Timeslot timeslot : timeslots) {
if (!modified.before(timeslot.time)) {
return timeslot.label;
}
}
if (!modified.before(this.lastYear)) {
// use YEAR and MONTH in a format based on current locale
return DateUtils.formatDateTime(context, modified.getTimeInMillis(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY);
} else {
return Integer.toString(modified.get(Calendar.YEAR));
}
}
private class Timeslot {
private final String label;
private final Calendar time;
Timeslot(String label, int month, int day) {
this.label = label;
this.time = Calendar.getInstance();
this.time.set(this.time.get(Calendar.YEAR), month, day, 0, 0, 0);
}
}
}
}

View file

@ -366,7 +366,7 @@ public class NoteServerSyncHelper {
switch (note.getStatus()) { switch (note.getStatus()) {
case LOCAL_EDITED: case LOCAL_EDITED:
Log.v(TAG, " ...create/edit"); Log.v(TAG, " ...create/edit");
if (note.getRemoteId() > 0) { if (note.getRemoteId() != null && note.getRemoteId() > 0) {
Log.v(TAG, " ...Note has remoteId → try to edit"); Log.v(TAG, " ...Note has remoteId → try to edit");
try { try {
remoteNote = notesClient.editNote(ssoAccount, note).getNote(); remoteNote = notesClient.editNote(ssoAccount, note).getNote();

View file

@ -1,5 +1,6 @@
package it.niedermann.owncloud.notes.persistence.dao; package it.niedermann.owncloud.notes.persistence.dao;
import androidx.lifecycle.LiveData;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.OnConflictStrategy; import androidx.room.OnConflictStrategy;
@ -31,15 +32,19 @@ public interface NoteDao {
@Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " + @Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " +
"NOTE.title LIKE :query OR content LIKE :query OR CATEGORY.title LIKE :query) AND (CATEGORY.title = :category OR CATEGORY.title LIKE :category + '/%' " + "NOTE.title LIKE :query OR content LIKE :query OR CATEGORY.title LIKE :query) AND (CATEGORY.title = :category OR CATEGORY.title LIKE :category + '/%' " +
") ORDER BY categoryId, favorite DESC, :sortingMethod") ") ORDER BY categoryId, favorite DESC, :sortingMethod")
List<NoteWithCategory> searchNotesByCategory(long accountId, String query, String category, CategorySortingMethod sortingMethod); LiveData<List<NoteWithCategory>> searchNotesByCategory(long accountId, String query, String category, String sortingMethod);
@Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " + @Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " +
"NOTE.title LIKE :query OR content LIKE :query OR CATEGORY.title LIKE :query) AND favorite = 1 ORDER BY categoryId DESC, :sortingMethod") "NOTE.title LIKE :query OR content LIKE :query OR CATEGORY.title LIKE :query) AND favorite = 1 ORDER BY categoryId DESC, :sortingMethod")
List<NoteWithCategory> searchNotesByCategoryFavorites(long accountId, String query, CategorySortingMethod sortingMethod); LiveData<List<NoteWithCategory>> searchNotesFavorites(long accountId, String query, String sortingMethod);
@Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " + @Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " +
"NOTE.title LIKE :query OR content LIKE :query) OR CATEGORY.title = '' ORDER BY categoryId DESC, :sortingMethod") "NOTE.title LIKE :query OR content LIKE :query) AND CATEGORY.title = '' ORDER BY categoryId DESC, :sortingMethod")
List<NoteWithCategory> searchNotesByUncategorized(long accountId, String query, CategorySortingMethod sortingMethod); LiveData<List<NoteWithCategory>> searchNotesUncategorized(long accountId, String query, String sortingMethod);
@Query("SELECT *, NOTE.title, CATEGORY.title as 'category' FROM NOTE INNER JOIN CATEGORY ON categoryId = CATEGORY.id WHERE NOTE.accountId = :accountId AND status != 'LOCAL_DELETED' AND ( " +
"NOTE.title LIKE :query OR content LIKE :query OR CATEGORY.title LIKE :query) ORDER BY categoryId DESC, :sortingMethod")
LiveData<List<NoteWithCategory>> searchNotesAll(long accountId, String query, String sortingMethod);
/** /**