diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewModel.java b/app/src/main/java/org/tasks/tags/TagPickerViewModel.java deleted file mode 100644 index 614977bb0..000000000 --- a/app/src/main/java/org/tasks/tags/TagPickerViewModel.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.tasks.tags; - -import static com.google.common.collect.Iterables.any; -import static org.tasks.Strings.isNullOrEmpty; - -import androidx.annotation.Nullable; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModel; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import javax.inject.Inject; -import org.tasks.data.TagData; -import org.tasks.data.TagDataDao; -import org.tasks.tags.CheckBoxTriStates.State; - -@SuppressWarnings({"WeakerAccess", "RedundantSuppression"}) -public class TagPickerViewModel extends ViewModel { - - private final MutableLiveData> tags = new MutableLiveData<>(); - private final CompositeDisposable disposables = new CompositeDisposable(); - - @Inject TagDataDao tagDataDao; - - private final Set selected = new HashSet<>(); - private final Set partiallySelected = new HashSet<>(); - private String text; - - public void observe(LifecycleOwner owner, Observer> observer) { - tags.observe(owner, observer); - } - - public void setSelected(List selected, @Nullable List partiallySelected) { - this.selected.addAll(selected); - if (partiallySelected != null) { - this.partiallySelected.addAll(partiallySelected); - } - } - - public ArrayList getSelected() { - return new ArrayList<>(selected); - } - - ArrayList getPartiallySelected() { - return new ArrayList<>(partiallySelected); - } - - public String getText() { - return text; - } - - public void search(String text) { - if (!text.equalsIgnoreCase(this.text)) { - disposables.add( - Single.fromCallable(() -> tagDataDao.searchTags(text)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::onUpdate)); - } - this.text = text; - } - - private void onUpdate(List results) { - if (!isNullOrEmpty(text) && !any(results, t -> text.equalsIgnoreCase(t.getName()))) { - results.add(0, new TagData(text)); - } - tags.setValue(results); - } - - @Override - protected void onCleared() { - super.onCleared(); - disposables.dispose(); - } - - State getState(TagData tagData) { - if (partiallySelected.contains(tagData)) { - return State.PARTIALLY_CHECKED; - } - return selected.contains(tagData) ? State.CHECKED : State.UNCHECKED; - } - - State toggle(TagData tagData, boolean checked) { - if (tagData.getId() == null) { - tagData = new TagData(tagData.getName()); - tagDataDao.createNew(tagData); - } - - partiallySelected.remove(tagData); - - if (checked) { - selected.add(tagData); - return State.CHECKED; - } else { - selected.remove(tagData); - return State.UNCHECKED; - } - } -} diff --git a/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt b/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt new file mode 100644 index 000000000..c41544488 --- /dev/null +++ b/app/src/main/java/org/tasks/tags/TagPickerViewModel.kt @@ -0,0 +1,88 @@ +package org.tasks.tags + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModel +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import org.tasks.Strings.isNullOrEmpty +import org.tasks.data.TagData +import org.tasks.data.TagDataDao +import org.tasks.tags.CheckBoxTriStates.State +import java.util.* +import javax.inject.Inject + +class TagPickerViewModel : ViewModel() { + private val tags = MutableLiveData>() + private val disposables = CompositeDisposable() + + @Inject lateinit var tagDataDao: TagDataDao + + private val selected: MutableSet = HashSet() + private val partiallySelected: MutableSet = HashSet() + var text: String? = null + private set + + fun observe(owner: LifecycleOwner, observer: Observer>) = + tags.observe(owner, observer) + + fun setSelected(selected: List, partiallySelected: List?) { + this.selected.addAll(selected) + if (partiallySelected != null) { + this.partiallySelected.addAll(partiallySelected) + } + } + + fun getSelected() = ArrayList(selected) + + fun getPartiallySelected() = ArrayList(partiallySelected) + + fun search(text: String) { + if (!text.equals(this.text, ignoreCase = true)) { + disposables.add( + Single.fromCallable { tagDataDao.searchTags(text) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { results: List -> onUpdate(results.toMutableList()) }) + } + this.text = text + } + + private fun onUpdate(results: MutableList) { + if (!isNullOrEmpty(text) && !results.any { text.equals(it.name, ignoreCase = true) }) { + results.add(0, TagData(text)) + } + tags.value = results + } + + override fun onCleared() { + super.onCleared() + disposables.dispose() + } + + fun getState(tagData: TagData): State { + if (partiallySelected.contains(tagData)) { + return State.PARTIALLY_CHECKED + } + return if (selected.contains(tagData)) State.CHECKED else State.UNCHECKED + } + + fun toggle(tagData: TagData, checked: Boolean): State { + var tagData = tagData + if (tagData.id == null) { + tagData = TagData(tagData.name) + tagDataDao.createNew(tagData) + } + partiallySelected.remove(tagData) + return if (checked) { + selected.add(tagData) + State.CHECKED + } else { + selected.remove(tagData) + State.UNCHECKED + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.java b/app/src/main/java/org/tasks/ui/TaskListViewModel.java deleted file mode 100644 index 30d3a1b63..000000000 --- a/app/src/main/java/org/tasks/ui/TaskListViewModel.java +++ /dev/null @@ -1,157 +0,0 @@ -package org.tasks.ui; - -import static com.todoroo.andlib.utility.AndroidUtilities.assertMainThread; -import static com.todoroo.andlib.utility.AndroidUtilities.assertNotMainThread; -import static com.todoroo.andlib.utility.DateUtilities.now; -import static io.reactivex.Single.fromCallable; -import static org.tasks.data.TaskListQuery.getQuery; - -import androidx.annotation.NonNull; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; -import androidx.lifecycle.ViewModel; -import androidx.paging.DataSource.Factory; -import androidx.paging.LivePagedListBuilder; -import androidx.paging.PagedList; -import androidx.sqlite.db.SimpleSQLiteQuery; -import com.todoroo.astrid.api.Filter; -import com.todoroo.astrid.dao.TaskDao; -import io.reactivex.Completable; -import io.reactivex.Single; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.schedulers.Schedulers; -import java.util.Collections; -import java.util.List; -import javax.inject.Inject; -import org.tasks.BuildConfig; -import org.tasks.data.SubtaskInfo; -import org.tasks.data.TaskContainer; -import org.tasks.preferences.Preferences; -import timber.log.Timber; - -@SuppressWarnings({"WeakerAccess", "RedundantSuppression"}) -public class TaskListViewModel extends ViewModel implements Observer> { - - private static final PagedList.Config PAGED_LIST_CONFIG = - new PagedList.Config.Builder().setPageSize(20).build(); - - @Inject Preferences preferences; - @Inject TaskDao taskDao; - private MutableLiveData> tasks = new MutableLiveData<>(); - private Filter filter; - private boolean manualSortFilter; - private final CompositeDisposable disposable = new CompositeDisposable(); - private LiveData> internal; - - public void setFilter(@NonNull Filter filter) { - if (!filter.equals(this.filter) - || !filter.getSqlQuery().equals(this.filter.getSqlQuery())) { - this.filter = filter; - tasks = new MutableLiveData<>(); - invalidate(); - } - manualSortFilter = - (filter.supportsManualSort() && preferences.isManualSort()) - || (filter.supportsAstridSorting() && preferences.isAstridSort()); - } - - public void observe(LifecycleOwner owner, Observer> observer) { - tasks.observe(owner, observer); - } - - public void searchByFilter(Filter filter) { - this.filter = filter; - invalidate(); - } - - private void removeObserver() { - if (internal != null) { - internal.removeObserver(this); - } - } - - public void invalidate() { - assertMainThread(); - - removeObserver(); - - if (filter == null) { - return; - } - - disposable.add( - Single.fromCallable(taskDao::getSubtaskInfo) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - subtasks -> { - if (manualSortFilter || !preferences.usePagedQueries()) { - performNonPagedQuery(subtasks); - } else { - performPagedListQuery(); - } - }, - Timber::e)); - } - - private void performNonPagedQuery(SubtaskInfo subtasks) { - disposable.add( - fromCallable(() -> taskDao.fetchTasks(s -> getQuery(preferences, filter, s), subtasks)) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(tasks::postValue, Timber::e)); - } - - private void performPagedListQuery() { - List queries = getQuery(preferences, filter, new SubtaskInfo()); - if (BuildConfig.DEBUG && queries.size() != 1) { - throw new RuntimeException("Invalid queries"); - } - SimpleSQLiteQuery query = new SimpleSQLiteQuery(queries.get(0)); - Timber.d("paged query: %s", query.getSql()); - Factory factory = taskDao.getTaskFactory(query); - LivePagedListBuilder builder = - new LivePagedListBuilder<>(factory, PAGED_LIST_CONFIG); - List current = tasks.getValue(); - if (current instanceof PagedList) { - Object lastKey = ((PagedList) current).getLastKey(); - if (lastKey instanceof Integer) { - builder.setInitialLoadKey((Integer) lastKey); - } - } - if (BuildConfig.DEBUG) { - builder.setFetchExecutor( - command -> - Completable.fromAction( - () -> { - assertNotMainThread(); - long start = now(); - command.run(); - Timber.d("*** paged list execution took %sms", now() - start); - }) - .subscribeOn(Schedulers.io()) - .subscribe()); - } - internal = builder.build(); - internal.observeForever(this); - } - - @Override - protected void onCleared() { - disposable.dispose(); - removeObserver(); - } - - public List getValue() { - List value = tasks.getValue(); - return value != null ? value : Collections.emptyList(); - } - - @Override - public void onChanged(PagedList taskContainers) { - tasks.setValue(taskContainers); - } -} diff --git a/app/src/main/java/org/tasks/ui/TaskListViewModel.kt b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt new file mode 100644 index 000000000..c754ffcbb --- /dev/null +++ b/app/src/main/java/org/tasks/ui/TaskListViewModel.kt @@ -0,0 +1,129 @@ +package org.tasks.ui + +import androidx.lifecycle.* +import androidx.paging.LivePagedListBuilder +import androidx.paging.PagedList +import androidx.sqlite.db.SimpleSQLiteQuery +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.dao.TaskDao +import io.reactivex.Completable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.schedulers.Schedulers +import org.tasks.BuildConfig +import org.tasks.data.SubtaskInfo +import org.tasks.data.TaskContainer +import org.tasks.data.TaskListQuery.getQuery +import org.tasks.preferences.Preferences +import timber.log.Timber +import javax.inject.Inject + +class TaskListViewModel : ViewModel(), Observer> { + @Inject lateinit var preferences: Preferences + @Inject lateinit var taskDao: TaskDao + + private var tasks = MutableLiveData>() + private var filter: Filter? = null + private var manualSortFilter = false + private val disposable = CompositeDisposable() + private var internal: LiveData>? = null + + fun setFilter(filter: Filter) { + if (filter != this.filter + || filter.getSqlQuery() != this.filter!!.getSqlQuery()) { + this.filter = filter + tasks = MutableLiveData() + invalidate() + } + manualSortFilter = (filter.supportsManualSort() && preferences.isManualSort + || filter.supportsAstridSorting() && preferences.isAstridSort) + } + + fun observe(owner: LifecycleOwner, observer: Observer>) = + tasks.observe(owner, observer) + + fun searchByFilter(filter: Filter?) { + this.filter = filter + invalidate() + } + + private fun removeObserver() = internal?.removeObserver(this) + + fun invalidate() { + AndroidUtilities.assertMainThread() + removeObserver() + if (filter == null) { + return + } + disposable.add( + Single.fromCallable { taskDao.getSubtaskInfo() } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + { subtasks: SubtaskInfo -> + if (manualSortFilter || !preferences.usePagedQueries()) { + performNonPagedQuery(subtasks) + } else { + performPagedListQuery() + } + }) { t: Throwable? -> Timber.e(t) }) + } + + private fun performNonPagedQuery(subtasks: SubtaskInfo) = + disposable.add( + Single.fromCallable { taskDao.fetchTasks({ s: SubtaskInfo? -> getQuery(preferences, filter!!, s!!) }, subtasks) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ value: List -> tasks.postValue(value) }) { t: Throwable? -> Timber.e(t) }) + + private fun performPagedListQuery() { + val queries = getQuery(preferences, filter!!, SubtaskInfo()) + if (BuildConfig.DEBUG && queries.size != 1) { + throw RuntimeException("Invalid queries") + } + val query = SimpleSQLiteQuery(queries[0]) + Timber.d("paged query: %s", query.sql) + val factory = taskDao.getTaskFactory(query) + val builder = LivePagedListBuilder(factory, PAGED_LIST_CONFIG) + val current = tasks.value!! + if (current is PagedList<*>) { + val lastKey = (current as PagedList).lastKey + if (lastKey is Int) { + builder.setInitialLoadKey(lastKey as Int?) + } + } + if (BuildConfig.DEBUG) { + builder.setFetchExecutor { command: Runnable -> + Completable.fromAction { + AndroidUtilities.assertNotMainThread() + val start = DateUtilities.now() + command.run() + Timber.d("*** paged list execution took %sms", DateUtilities.now() - start) + } + .subscribeOn(Schedulers.io()) + .subscribe() + } + } + internal = builder.build() + internal!!.observeForever(this) + } + + override fun onCleared() { + disposable.dispose() + removeObserver() + } + + val value: List + get() = tasks.value ?: emptyList() + + override fun onChanged(taskContainers: PagedList) { + tasks.value = taskContainers + } + + companion object { + private val PAGED_LIST_CONFIG = PagedList.Config.Builder().setPageSize(20).build() + } +} \ No newline at end of file