From 8b2ed5b1e9177fd5f36d1badf556bb89be20190f Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 27 May 2020 10:06:30 -0500 Subject: [PATCH] Collapsible sort group headers --- .../CaldavManualSortTaskAdapterTest.kt | 2 +- .../astrid/adapter/CaldavTaskAdapterTest.kt | 2 +- .../GoogleTaskManualSortAdapterTest.kt | 2 +- .../todoroo/astrid/activity/MainActivity.kt | 1 + .../astrid/activity/TaskListFragment.kt | 11 +- .../astrid/adapter/CaldavTaskAdapter.kt | 6 + .../com/todoroo/astrid/adapter/TaskAdapter.kt | 26 +++- .../astrid/adapter/TaskAdapterDataSource.kt | 4 +- .../com/todoroo/astrid/core/SortHelper.java | 16 +++ .../java/org/tasks/data/TaskContainer.java | 25 +++- .../org/tasks/data/TaskListQueryRecursive.kt | 7 +- .../org/tasks/injection/ApplicationModule.kt | 4 + .../java/org/tasks/tasklist/AdapterSection.kt | 3 + .../java/org/tasks/tasklist/DiffCallback.kt | 21 +++- .../tasklist/DragAndDropRecyclerAdapter.kt | 114 +++++++++++++----- .../org/tasks/tasklist/HeaderViewHolder.kt | 73 +++++++++++ .../tasklist/PagedListRecyclerAdapter.kt | 6 +- .../org/tasks/tasklist/SectionedDataSource.kt | 111 +++++++++++++++++ .../tasks/tasklist/TaskListRecyclerAdapter.kt | 18 +-- .../org/tasks/tasklist/TaskViewHolder.java | 28 +++-- .../org/tasks/tasklist/ViewHolderFactory.java | 79 ------------ .../org/tasks/tasklist/ViewHolderFactory.kt | 62 ++++++++++ .../main/res/layout/task_adapter_header.xml | 16 +++ app/src/main/res/values/strings.xml | 2 + 24 files changed, 489 insertions(+), 150 deletions(-) create mode 100644 app/src/main/java/org/tasks/tasklist/AdapterSection.kt create mode 100644 app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt create mode 100644 app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt delete mode 100644 app/src/main/java/org/tasks/tasklist/ViewHolderFactory.java create mode 100644 app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt create mode 100644 app/src/main/res/layout/task_adapter_header.xml diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt index 7b52a47be..c3f4cfebc 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavManualSortTaskAdapterTest.kt @@ -41,7 +41,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() { private val dataSource = object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override fun getItemCount() = tasks.size + override fun getTaskCount() = tasks.size } @Before diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt index 881b6a784..b833b355d 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/CaldavTaskAdapterTest.kt @@ -36,7 +36,7 @@ class CaldavTaskAdapterTest : InjectingTestCase() { adapter.setDataSource(object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override fun getItemCount() = tasks.size + override fun getTaskCount() = tasks.size }) } diff --git a/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt b/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt index 1a80d103a..f4c57cd6e 100644 --- a/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt +++ b/app/src/androidTest/java/com/todoroo/astrid/adapter/GoogleTaskManualSortAdapterTest.kt @@ -40,7 +40,7 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() { private val dataSource = object : TaskAdapterDataSource { override fun getItem(position: Int) = tasks[position] - override fun getItemCount() = tasks.size + override fun getTaskCount() = tasks.size } @Test diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt index 030b8aa8a..686085e13 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt @@ -404,6 +404,7 @@ class MainActivity : InjectingAppCompatActivity(), TaskListFragmentCallbackHandl } override fun sortChanged(reload: Boolean) { + taskListFragment?.clearCollapsed() localBroadcastManager.broadcastRefresh() if (reload) { openTaskListFragment(filter, true) diff --git a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt index bdd7fcdd5..ae92d6f79 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/TaskListFragment.kt @@ -167,7 +167,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI if (savedInstanceState != null) { val longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS) if (longArray?.isNotEmpty() == true) { - taskAdapter.setSelected(*longArray) + taskAdapter.setSelected(longArray.toList()) startActionMode() } } @@ -185,6 +185,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI val selectedTaskIds: List = taskAdapter.getSelected() outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray()) outState.putString(EXTRA_SEARCH, searchQuery) + outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray()) } override fun onCreateView( @@ -197,6 +198,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI // set up list adapters taskAdapter = taskAdapterProvider.createTaskAdapter(filter) + taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED)) taskListViewModel = ViewModelProvider(requireActivity()).get(TaskListViewModel::class.java) if (savedInstanceState != null) { searchQuery = savedInstanceState.getString(EXTRA_SEARCH) @@ -231,13 +233,13 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI if (recyclerAdapter !is PagedListRecyclerAdapter) { setAdapter( PagedListRecyclerAdapter( - taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)) + taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao, preferences)) return } } else if (recyclerAdapter !is DragAndDropRecyclerAdapter) { setAdapter( DragAndDropRecyclerAdapter( - taskAdapter, recyclerView, viewHolderFactory, this, tasks as MutableList, taskDao)) + taskAdapter, recyclerView, viewHolderFactory, this, tasks as MutableList, taskDao, preferences)) return } recyclerAdapter!!.submitList(tasks) @@ -740,6 +742,8 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString()).show() } + fun clearCollapsed() = taskAdapter.clearCollapsed() + companion object { const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$ const val GTASK_METADATA_JOIN = "googletask" // $NON-NLS-1$ @@ -748,6 +752,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI const val ACTION_DELETED = "action_deleted" private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids" private const val EXTRA_SEARCH = "extra_search" + private const val EXTRA_COLLAPSED = "extra_collapsed" private const val VOICE_RECOGNITION_REQUEST_CODE = 1234 private const val EXTRA_FILTER = "extra_filter" private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker" diff --git a/app/src/main/java/com/todoroo/astrid/adapter/CaldavTaskAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/CaldavTaskAdapter.kt index 0bf800596..e1f73d2b5 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/CaldavTaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/CaldavTaskAdapter.kt @@ -12,6 +12,9 @@ open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao, override fun minIndent(nextPosition: Int, task: TaskContainer): Int { (nextPosition until count).forEach { + if (isHeader(it)) { + return 0 + } if (!taskIsChild(task, it)) { return getTask(it).indent } @@ -79,6 +82,9 @@ open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao, private fun taskIsChild(source: TaskContainer, destinationIndex: Int): Boolean { (destinationIndex downTo 0).forEach { + if (isHeader(it)) { + return false + } when (getTask(it).parent) { 0L -> return false source.parent -> return false diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt index abbeb6b77..cf66a6351 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapter.kt @@ -11,10 +11,11 @@ import java.util.* open class TaskAdapter { private val selected = HashSet() + private val collapsed = HashSet() private lateinit var dataSource: TaskAdapterDataSource val count: Int - get() = dataSource.getItemCount() + get() = dataSource.getTaskCount() fun setDataSource(dataSource: TaskAdapterDataSource) { this.dataSource = dataSource @@ -25,15 +26,22 @@ open class TaskAdapter { fun getSelected(): ArrayList = ArrayList(selected) - fun setSelected(vararg ids: Long) = setSelected(ids.toList()) - fun setSelected(ids: Collection) { - selected.clear() + clearSelections() selected.addAll(ids) } fun clearSelections() = selected.clear() + fun getCollapsed(): ArrayList = ArrayList(collapsed) + + fun setCollapsed(groups: LongArray?) { + clearCollapsed() + groups?.toList()?.let(collapsed::addAll) + } + + fun clearCollapsed() = collapsed.clear() + open fun getIndent(task: TaskContainer): Int = task.getIndent() open fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean = false @@ -53,12 +61,22 @@ open class TaskAdapter { } } + fun toggleCollapsed(group: Long) { + if (collapsed.contains(group)) { + collapsed.remove(group) + } else { + collapsed.add(group) + } + } + open fun supportsParentingOrManualSort(): Boolean = false open fun supportsManualSorting(): Boolean = false open fun moved(from: Int, to: Int, indent: Int) {} + fun isHeader(position: Int): Boolean = dataSource.isHeader(position) + fun getTask(position: Int): TaskContainer = dataSource.getItem(position) fun getItemUuid(position: Int): String = getTask(position).uuid diff --git a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt index 0cea3577a..dd1c6f3ed 100644 --- a/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt +++ b/app/src/main/java/com/todoroo/astrid/adapter/TaskAdapterDataSource.kt @@ -5,5 +5,7 @@ import org.tasks.data.TaskContainer interface TaskAdapterDataSource { fun getItem(position: Int): TaskContainer - fun getItemCount(): Int + fun getTaskCount(): Int + + fun isHeader(position: Int): Boolean = false } \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java index 495844936..1b3a8f9b4 100644 --- a/app/src/main/java/com/todoroo/astrid/core/SortHelper.java +++ b/app/src/main/java/com/todoroo/astrid/core/SortHelper.java @@ -11,6 +11,7 @@ import static org.tasks.db.QueryUtils.showHidden; import static org.tasks.db.QueryUtils.showRecentlyCompleted; import android.annotation.SuppressLint; +import androidx.annotation.Nullable; import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Order; import com.todoroo.astrid.data.Task; @@ -122,6 +123,21 @@ public class SortHelper { return order; } + public static @Nullable String getSortGroup(int sortType) { + switch (sortType) { + case SORT_DUE: + return "tasks.dueDate"; + case SORT_IMPORTANCE: + return "tasks.importance"; + case SORT_MODIFIED: + return "tasks.modified"; + case SORT_CREATED: + return "tasks.created"; + default: + return null; + } + } + public static String orderSelectForSortTypeRecursive(int sortType) { String select; switch (sortType) { diff --git a/app/src/main/java/org/tasks/data/TaskContainer.java b/app/src/main/java/org/tasks/data/TaskContainer.java index 481d3bccb..ab8b2bc0e 100644 --- a/app/src/main/java/org/tasks/data/TaskContainer.java +++ b/app/src/main/java/org/tasks/data/TaskContainer.java @@ -11,6 +11,7 @@ public class TaskContainer { @Embedded public Location location; public String tags; public int children; + public Long sortGroup; public long primarySort; public long secondarySort; public int indent; @@ -52,6 +53,10 @@ public class TaskContainer { return task.hasDueDate(); } + public boolean hasDueTime() { + return task.hasDueTime(); + } + public boolean isOverdue() { return task.isOverdue(); } @@ -107,14 +112,24 @@ public class TaskContainer { && Objects.equals(googletask, that.googletask) && Objects.equals(caldavTask, that.caldavTask) && Objects.equals(location, that.location) - && Objects.equals(tags, that.tags); + && Objects.equals(tags, that.tags) + && Objects.equals(sortGroup, that.sortGroup); } @Override public int hashCode() { - return Objects - .hash(task, googletask, caldavTask, location, tags, children, primarySort, - secondarySort, indent, targetIndent); + return Objects.hash( + task, + googletask, + caldavTask, + location, + tags, + children, + sortGroup, + primarySort, + secondarySort, + indent, + targetIndent); } @Override @@ -133,6 +148,8 @@ public class TaskContainer { + '\'' + ", children=" + children + + ", sortGroup=" + + sortGroup + ", primarySort=" + primarySort + ", secondarySort=" diff --git a/app/src/main/java/org/tasks/data/TaskListQueryRecursive.kt b/app/src/main/java/org/tasks/data/TaskListQueryRecursive.kt index c48da94d0..d77c391ee 100644 --- a/app/src/main/java/org/tasks/data/TaskListQueryRecursive.kt +++ b/app/src/main/java/org/tasks/data/TaskListQueryRecursive.kt @@ -22,6 +22,7 @@ internal object TaskListQueryRecursive { TaskListQuery.FIELDS.plus(listOf( field("(${Query.select(field("group_concat(distinct(tag_uid))")).from(Tag.TABLE).where(Task.ID.eq(Tag.TASK))} GROUP BY ${Tag.TASK})").`as`("tags"), field("indent"), + field("sort_group").`as`("sortGroup"), field("children"), field("primary_sort").`as`("primarySort"), field("secondary_sort").`as`("secondarySort"))).toTypedArray() @@ -94,10 +95,10 @@ internal object TaskListQueryRecursive { val sortSelect = SortHelper.orderSelectForSortTypeRecursive(sortMode) val withClause =""" CREATE TEMPORARY TABLE `recursive_tasks` AS - WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField, primary_sort, secondary_sort) AS ( - SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, $sortField as primary_sort, NULL as secondarySort FROM tasks + WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField, primary_sort, secondary_sort, sort_group) AS ( + SELECT tasks._id, 0 as parent, tasks.collapsed as collapsed, 0 as hidden, 0 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, $sortField as primary_sort, NULL as secondarySort, ${SortHelper.getSortGroup(sortMode)} FROM tasks $parentQuery - UNION ALL SELECT tasks._id, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, recursive_tasks.primary_sort as primary_sort, $sortField as secondary_sort FROM tasks + UNION ALL SELECT tasks._id, recursive_tasks.task as parent, tasks.collapsed as collapsed, CASE WHEN recursive_tasks.collapsed > 0 OR recursive_tasks.hidden > 0 THEN 1 ELSE 0 END as hidden, recursive_tasks.indent+1 AS sort_indent, UPPER(tasks.title) AS sort_title, $sortSelect, recursive_tasks.primary_sort as primary_sort, $sortField as secondary_sort, recursive_tasks.sort_group FROM tasks $subtaskQuery ORDER BY sort_indent DESC, ${SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)} ) SELECT * FROM recursive_tasks diff --git a/app/src/main/java/org/tasks/injection/ApplicationModule.kt b/app/src/main/java/org/tasks/injection/ApplicationModule.kt index 36b7db123..ea59041c4 100644 --- a/app/src/main/java/org/tasks/injection/ApplicationModule.kt +++ b/app/src/main/java/org/tasks/injection/ApplicationModule.kt @@ -23,6 +23,10 @@ class ApplicationModule(@get:Provides @get:ForApplication val context: Context) val locale: Locale get() = Locale.getInstance(context) + @Provides + @ApplicationScope + fun getJavaLocale(locale: Locale): java.util.Locale = locale.locale + @Provides @ApplicationScope fun getNotificationDao(db: Database): NotificationDao = db.notificationDao() diff --git a/app/src/main/java/org/tasks/tasklist/AdapterSection.kt b/app/src/main/java/org/tasks/tasklist/AdapterSection.kt new file mode 100644 index 000000000..736b93155 --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/AdapterSection.kt @@ -0,0 +1,3 @@ +package org.tasks.tasklist + +data class AdapterSection(var firstPosition: Int, val value: Long, var sectionedPosition: Int = 0, var collapsed: Boolean = false) \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/DiffCallback.kt b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt index 1b686c58a..62e651366 100644 --- a/app/src/main/java/org/tasks/tasklist/DiffCallback.kt +++ b/app/src/main/java/org/tasks/tasklist/DiffCallback.kt @@ -2,21 +2,32 @@ package org.tasks.tasklist import androidx.recyclerview.widget.DiffUtil import com.todoroo.astrid.adapter.TaskAdapter -import org.tasks.data.TaskContainer -internal class DiffCallback(private val old: List, private val new: List, @Deprecated("") private val adapter: TaskAdapter) : DiffUtil.Callback() { +internal class DiffCallback(private val old: SectionedDataSource, private val new: SectionedDataSource, @Deprecated("") private val adapter: TaskAdapter) : DiffUtil.Callback() { override fun getOldListSize() = old.size override fun getNewListSize() = new.size override fun areItemsTheSame(oldPosition: Int, newPosition: Int): Boolean { - return old[oldPosition].id == new[newPosition].id + val wasHeader = old.isHeader(oldPosition) + val isHeader = new.isHeader(newPosition) + if (wasHeader != isHeader) { + return false + } + return if (isHeader) { + old.sortMode == new.sortMode && old.getHeaderValue(oldPosition) == new.getHeaderValue(newPosition) + } else { + old.getItem(oldPosition).id == new.getItem(newPosition).id + } } override fun areContentsTheSame(oldPosition: Int, newPosition: Int): Boolean { - val oldItem = old[oldPosition] - val newItem = new[newPosition] + if (new.isHeader(newPosition)) { + return old.getSection(oldPosition).collapsed == new.getSection(newPosition).collapsed + } + val oldItem = old.getItem(oldPosition) + val newItem = new.getItem(newPosition) return oldItem == newItem && oldItem.getIndent() == adapter.getIndent(newItem) } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt index 798fad95d..1250acb02 100644 --- a/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/DragAndDropRecyclerAdapter.kt @@ -1,6 +1,7 @@ package org.tasks.tasklist import android.graphics.Canvas +import android.view.ViewGroup import androidx.core.util.Pair import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ItemTouchHelper @@ -11,11 +12,13 @@ import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.utility.Flags +import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.CompositeDisposable import io.reactivex.schedulers.Schedulers import io.reactivex.subjects.PublishSubject import org.tasks.data.TaskContainer +import org.tasks.preferences.Preferences import java.util.* import kotlin.math.max import kotlin.math.min @@ -25,27 +28,61 @@ class DragAndDropRecyclerAdapter( private val recyclerView: RecyclerView, viewHolderFactory: ViewHolderFactory, private val taskList: TaskListFragment, - private var list: MutableList, - taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { - private val publishSubject = PublishSubject.create>() + tasks: MutableList, + taskDao: TaskDao, + preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) { + private var list: SectionedDataSource + private val publishSubject = PublishSubject.create() private val disposables = CompositeDisposable() - private val updates: Queue, DiffUtil.DiffResult>> = LinkedList() + private val updates: Queue> = LinkedList() private var dragging = false + private val disableHeaders: Boolean + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val viewType = getItemViewType(position) + if (viewType == 1) { + val headerSection = list.getSection(position) + (holder as HeaderViewHolder).bind(taskList.getFilter(), preferences.sortMode, headerSection) + } else { + super.onBindViewHolder(holder, position) + } + } + + override fun getItemViewType(position: Int) = if (list.isHeader(position)) 1 else 0 + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = if (viewType == 1) { + viewHolderFactory.newHeaderViewHolder(parent, this::toggleGroup) + } else { + super.onCreateViewHolder(parent, viewType) + } + + private fun toggleGroup(group: Long) { + adapter.toggleCollapsed(group) + taskList.loadTaskListContent() + } + override fun dragAndDropEnabled() = adapter.supportsParentingOrManualSort() - override fun getItem(position: Int) = list[position] + override fun isHeader(position: Int): Boolean = list.isHeader(position) + + override fun getItem(position: Int): TaskContainer = list.getItem(position) - override fun submitList(list: List) = publishSubject.onNext(list as MutableList) + override fun submitList(list: List) { + disposables.add( + Single.fromCallable { SectionedDataSource(list as MutableList, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet()) } + .subscribeOn(Schedulers.computation()) + .subscribe(publishSubject::onNext)) + } private fun calculateDiff( - last: Pair, DiffUtil.DiffResult>, next: MutableList): Pair, DiffUtil.DiffResult> { + last: Pair, next: SectionedDataSource): Pair { AndroidUtilities.assertNotMainThread() val cb = DiffCallback(last.first!!, next, adapter) val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE) return Pair.create(next, result) } - private fun applyDiff(update: Pair, DiffUtil.DiffResult>) { + private fun applyDiff(update: Pair) { AndroidUtilities.assertMainThread() updates.add(update) if (!dragging) { @@ -67,9 +104,9 @@ class DragAndDropRecyclerAdapter( override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose() - override fun getItemCount(): Int { - return list.size - } + override fun getTaskCount() = list.taskCount + + override fun getItemCount() = list.size private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() { private var from = -1 @@ -85,10 +122,14 @@ class DragAndDropRecyclerAdapter( } } - override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) { - makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0) - } else { - makeMovementFlags(0, 0) + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return if (adapter.isHeader(viewHolder.adapterPosition)) { + makeMovementFlags(0, 0) + } else if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) { + makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0) + } else { + makeMovementFlags(0, 0) + } } override fun onMove( @@ -99,7 +140,8 @@ class DragAndDropRecyclerAdapter( val fromPosition = src.adapterPosition val toPosition = target.adapterPosition val source = src as TaskViewHolder - if (!adapter.canMove(source.task, fromPosition, (target as TaskViewHolder).task, toPosition)) { + val isHeader = isHeader(toPosition) + if (!isHeader && !adapter.canMove(source.task, fromPosition, (target as TaskViewHolder).task, toPosition)) { return false } if (from == -1) { @@ -108,14 +150,19 @@ class DragAndDropRecyclerAdapter( } to = toPosition notifyItemMoved(fromPosition, toPosition) + if (isHeader) { + val offset = if (fromPosition < toPosition) -1 else 1 + list.moveSection(toPosition, offset) + } updateIndents(source, from, to) return true } private fun updateIndents(source: TaskViewHolder?, from: Int, to: Int) { val task = source!!.task - source.minIndent = if (to == 0 || to == itemCount - 1) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task) - source.maxIndent = if (to == 0) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task) + val previousIsHeader = to > 0 && isHeader(to - 1) + source.minIndent = if (to == 0 || to == itemCount - 1 || previousIsHeader) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task) + source.maxIndent = if (to == 0 || previousIsHeader) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task) } override fun onChildDraw( @@ -191,7 +238,14 @@ class DragAndDropRecyclerAdapter( throw UnsupportedOperationException() } - private fun moved(from: Int, to: Int, indent: Int) { + private fun moved(fromOrig: Int, to: Int, indent: Int) { + val from = if (fromOrig == to) { + to + } else if (fromOrig > to && isHeader(fromOrig)) { + from - 1 + } else { + from + } adapter.moved(from, to, indent) val task: TaskContainer = list.removeAt(from) list.add(if (from < to) to - 1 else to, task) @@ -204,16 +258,18 @@ class DragAndDropRecyclerAdapter( } init { + val filter = taskList.getFilter() + disableHeaders = !filter.supportsSorting() || preferences.isManualSort && filter.supportsManualSort() ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView) - val initial = Pair.create, DiffUtil.DiffResult>(list, null) - disposables.add( - publishSubject - .observeOn(Schedulers.computation()) - .scan(initial, { last: Pair, DiffUtil.DiffResult>, next: List -> - calculateDiff(last, next.toMutableList()) - }) - .skip(1) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { update: Pair, DiffUtil.DiffResult> -> applyDiff(update) }) + list = SectionedDataSource(tasks, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet()) + val initial = Pair.create(list, null) + disposables.add(publishSubject + .observeOn(Schedulers.computation()) + .scan(initial, { last: Pair, next: SectionedDataSource -> + calculateDiff(last, next) + }) + .skip(1) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { update: Pair -> applyDiff(update) }) } } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt b/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt new file mode 100644 index 000000000..9925a46a6 --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/HeaderViewHolder.kt @@ -0,0 +1,73 @@ +package org.tasks.tasklist + +import android.content.Context +import android.view.View +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.todoroo.andlib.utility.DateUtilities +import com.todoroo.astrid.api.Filter +import com.todoroo.astrid.core.SortHelper +import org.tasks.R +import org.tasks.date.DateTimeUtils.newDateTime +import org.threeten.bp.format.FormatStyle +import java.util.* + +class HeaderViewHolder( + private val context: Context, + private val locale: Locale, + view: View, + callback: (Long) -> Unit) : RecyclerView.ViewHolder(view) { + + private val header: TextView = view.findViewById(R.id.header) + private var sortGroup = -1L + + fun bind(filter: Filter, sortMode: Int, section: AdapterSection) { + sortGroup = section.value + val header: String? = if (filter.supportsSorting()) getHeader(sortMode, sortGroup) else null + + if (header == null) { + this.header.visibility = View.GONE + } else { + this.header.visibility = View.VISIBLE + this.header.text = header + this.header.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, if (section.collapsed) R.drawable.ic_keyboard_arrow_up_black_18dp else R.drawable.ic_keyboard_arrow_down_black_18dp, 0) + this.header.setTextColor( + context.getColor( + if (sortMode == SortHelper.SORT_DUE && sortGroup > 0 && newDateTime(sortGroup).plusDays(1).startOfDay().isBeforeNow) R.color.overdue else R.color.text_secondary)) + } + } + + private fun getHeader(sortMode: Int, group: Long): String { + return when { + sortMode == SortHelper.SORT_IMPORTANCE -> context.getString(priorityToString(group.toInt())) + group == 0L -> context.getString(if (sortMode == SortHelper.SORT_DUE) { + R.string.no_due_date + } else { + R.string.no_date + }) + sortMode == SortHelper.SORT_CREATED -> + context.getString(R.string.sort_created_group, getDateString(group)) + sortMode == SortHelper.SORT_MODIFIED -> + context.getString(R.string.sort_modified_group, getDateString(group)) + else -> getDateString(group, false) + } + } + + private fun getDateString(value: Long, lowercase: Boolean = true) = + DateUtilities.getRelativeDay(context, value, locale, FormatStyle.FULL, lowercase) + + @StringRes + private fun priorityToString(priority: Int) = when (priority) { + 0 -> R.string.filter_high_priority + 1 -> R.string.filter_medium_priority + 2 -> R.string.filter_low_priority + else -> R.string.filter_no_priority + } + + init { + header.setOnClickListener { + callback.invoke(sortGroup) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt index 4475de877..9667ba14d 100644 --- a/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/PagedListRecyclerAdapter.kt @@ -8,6 +8,7 @@ import com.todoroo.astrid.activity.TaskListFragment import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.dao.TaskDao import org.tasks.data.TaskContainer +import org.tasks.preferences.Preferences class PagedListRecyclerAdapter( adapter: TaskAdapter, @@ -15,7 +16,8 @@ class PagedListRecyclerAdapter( viewHolderFactory: ViewHolderFactory, taskList: TaskListFragment, list: List, - taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { + taskDao: TaskDao, + preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) { private val differ: AsyncPagedListDiffer = AsyncPagedListDiffer(this, AsyncDifferConfig.Builder(ItemCallback()).build()) @@ -34,6 +36,8 @@ class PagedListRecyclerAdapter( override fun getItemCount() = differ.itemCount + override fun getTaskCount() = itemCount + init { if (list is PagedList<*>) { differ.submitList(list as PagedList?) diff --git a/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt b/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt new file mode 100644 index 000000000..99de58f6d --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/SectionedDataSource.kt @@ -0,0 +1,111 @@ +package org.tasks.tasklist + +import android.util.SparseArray +import com.todoroo.astrid.core.SortHelper +import org.tasks.data.TaskContainer +import org.tasks.date.DateTimeUtils +import java.util.* + +class SectionedDataSource constructor(tasks: List, disableHeaders: Boolean, val sortMode: Int, private val collapsed: MutableSet) { + + private val tasks = tasks.toMutableList() + + private val sections = if (disableHeaders) { + SparseArray() + } else { + getSections() + } + + fun getItem(position: Int): TaskContainer = tasks[sectionedPositionToPosition(position)] + + fun getHeaderValue(position: Int): Long = sections[position]!!.value + + fun isHeader(position: Int) = sections[position] != null + + private fun sectionedPositionToPosition(sectionedPosition: Int): Int { + if (isHeader(sectionedPosition)) { + return sections[sectionedPosition].firstPosition + } + + var offset = 0 + for (i in 0 until sections.size()) { + val section = sections.valueAt(i) + if (section.sectionedPosition > sectionedPosition) { + break + } + --offset + } + return sectionedPosition + offset + } + + val taskCount: Int + get() = tasks.size + + val size: Int + get() = tasks.size + sections.size() + + fun getSection(position: Int): AdapterSection = sections[position] + + fun add(position: Int, task: TaskContainer) = tasks.add(sectionedPositionToPosition(position), task) + + fun removeAt(position: Int): TaskContainer = tasks.removeAt(sectionedPositionToPosition(position)) + + private fun getSections(): SparseArray { + val sections = ArrayList() + for (i in tasks.indices) { + val task = tasks[i] + val sortGroup = task.sortGroup ?: continue + val header = if (sortMode == SortHelper.SORT_IMPORTANCE || sortGroup == 0L) { + sortGroup + } else { + DateTimeUtils.newDateTime(sortGroup).startOfDay().millis + } + val isCollapsed = collapsed.contains(header) + if (i == 0) { + sections.add(AdapterSection(i, header, 0, isCollapsed)) + } else { + val previous = tasks[i - 1].sortGroup + when (sortMode) { + SortHelper.SORT_IMPORTANCE -> if (header != previous) { + sections.add(AdapterSection(i, header, 0, isCollapsed)) + } + else -> if (previous > 0 && header != DateTimeUtils.newDateTime(previous).startOfDay().millis) { + sections.add(AdapterSection(i, header, 0, isCollapsed)) + } + } + } + } + + var adjustment = 0 + for (i in sections.indices) { + val section = sections[i] + section.firstPosition -= adjustment + if (section.collapsed) { + val next = sections.getOrNull(i + 1)?.firstPosition?.minus(adjustment) ?: tasks.size + tasks.subList(section.firstPosition, next).clear() + adjustment += next - section.firstPosition + } + } + + return setSections(sections) + } + + private fun setSections(newSections: List): SparseArray { + val sections = SparseArray() + newSections.forEachIndexed { index, section -> + section.sectionedPosition = section.firstPosition + index + sections.append(section.sectionedPosition, section) + } + return sections + } + + fun moveSection(toPosition: Int, offset: Int) { + val old = sections[toPosition] + sections.remove(toPosition) + val newSectionedPosition = old.sectionedPosition + offset + val previousSection = if (isHeader(newSectionedPosition - 1)) sections[newSectionedPosition - 1] else null + val newFirstPosition = previousSection?.firstPosition ?: old.firstPosition + offset + val new = AdapterSection(newFirstPosition, old.value, newSectionedPosition, old.collapsed) + sections.append(new.sectionedPosition, new) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt index b21677526..b10d42981 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt +++ b/app/src/main/java/org/tasks/tasklist/TaskListRecyclerAdapter.kt @@ -10,22 +10,24 @@ import com.todoroo.astrid.api.Filter import com.todoroo.astrid.dao.TaskDao import org.tasks.data.TaskContainer import org.tasks.intents.TaskIntents +import org.tasks.preferences.Preferences import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks abstract class TaskListRecyclerAdapter internal constructor( private val adapter: TaskAdapter, - private val viewHolderFactory: ViewHolderFactory, + internal val viewHolderFactory: ViewHolderFactory, private val taskList: TaskListFragment, - private val taskDao: TaskDao) : RecyclerView.Adapter(), ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource { + private val taskDao: TaskDao, + internal val preferences: Preferences) + : RecyclerView.Adapter(), ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource { - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder { - return viewHolderFactory.newViewHolder(parent, this) - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = viewHolderFactory.newViewHolder(parent, this) - override fun onBindViewHolder(holder: TaskViewHolder, position: Int) { + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val sortMode = preferences.sortMode val task = getItem(position) if (task != null) { - holder.bindView(task, taskList.getFilter()) + (holder as TaskViewHolder).bindView(task, taskList.getFilter(), sortMode) holder.isMoving = false val indent = adapter.getIndent(task) task.setIndent(indent) @@ -85,8 +87,6 @@ abstract class TaskListRecyclerAdapter internal constructor( protected abstract fun dragAndDropEnabled(): Boolean - abstract override fun getItem(position: Int): TaskContainer - abstract fun submitList(list: List) override fun onInserted(position: Int, count: Int) { diff --git a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.java b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.java index 309f51961..bf3d1892d 100644 --- a/app/src/main/java/org/tasks/tasklist/TaskViewHolder.java +++ b/app/src/main/java/org/tasks/tasklist/TaskViewHolder.java @@ -1,6 +1,8 @@ package org.tasks.tasklist; import static com.todoroo.andlib.utility.DateUtilities.getRelativeDateTime; +import static com.todoroo.andlib.utility.DateUtilities.getTimeString; +import static org.tasks.date.DateTimeUtils.newDateTime; import android.annotation.SuppressLint; import android.app.Activity; @@ -18,13 +20,14 @@ import butterknife.OnLongClick; import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.core.SortHelper; import com.todoroo.astrid.service.TaskCompleter; import com.todoroo.astrid.ui.CheckableImageView; import java.util.List; +import java.util.Locale; import org.tasks.R; import org.tasks.data.TaskContainer; import org.tasks.dialogs.Linkify; -import org.tasks.locale.Locale; import org.tasks.preferences.Preferences; import org.tasks.ui.CheckBoxProvider; import org.tasks.ui.ChipProvider; @@ -42,6 +45,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { private final int selectedColor; private final int rowPadding; private final Linkify linkify; + private final Locale locale; private final CheckBoxProvider checkBoxProvider; private final int textColorOverdue; private final ChipProvider chipProvider; @@ -93,7 +97,8 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { int background, int selectedColor, int rowPadding, - Linkify linkify) { + Linkify linkify, + Locale locale) { super(view); this.context = context; this.preferences = preferences; @@ -108,6 +113,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { this.selectedColor = selectedColor; this.rowPadding = rowPadding; this.linkify = linkify; + this.locale = locale; ButterKnife.bind(this, view); if (preferences.getBoolean(R.string.p_fullTaskTitle, false)) { @@ -188,14 +194,14 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { return Math.round(indent * getShiftSize()); } - void bindView(TaskContainer task, Filter filter) { + void bindView(TaskContainer task, Filter filter, int sortMode) { this.task = task; this.indent = task.indent; nameView.setText(task.getTitle()); hiddenIcon.setVisibility(task.isHidden() ? View.VISIBLE : View.GONE); setupTitleAndCheckbox(); - setupDueDate(); + setupDueDate(sortMode); setupChips(filter); if (preferences.getBoolean(R.string.p_show_description, true)) { description.setText(task.getNotes()); @@ -233,16 +239,20 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { completeBox.invalidate(); } - private void setupDueDate() { + private void setupDueDate(int sortMode) { if (task.hasDueDate()) { if (task.isOverdue()) { dueDate.setTextColor(textColorOverdue); } else { dueDate.setTextColor(textColorSecondary); } - String dateValue = - getRelativeDateTime( - context, task.getDueDate(), Locale.getInstance().getLocale(), FormatStyle.MEDIUM); + String dateValue; + if (sortMode == SortHelper.SORT_DUE && task.sortGroup != null && newDateTime(task.sortGroup).startOfDay().equals(newDateTime(task.getDueDate()).startOfDay())) { + dateValue = + task.hasDueTime() ? getTimeString(context, newDateTime(task.getDueDate())) : null; + } else { + dateValue = getRelativeDateTime(context, task.getDueDate(), locale, FormatStyle.MEDIUM); + } dueDate.setText(dateValue); dueDate.setVisibility(View.VISIBLE); } else { @@ -332,7 +342,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder { return maxIndent; } - interface ViewHolderCallbacks { + public interface ViewHolderCallbacks { void onCompletedTask(TaskContainer task, boolean newState); diff --git a/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.java b/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.java deleted file mode 100644 index 945f8b90e..000000000 --- a/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.tasks.tasklist; - -import static com.todoroo.andlib.utility.AndroidUtilities.convertDpToPixels; -import static org.tasks.preferences.ResourceResolver.getData; -import static org.tasks.preferences.ResourceResolver.getResourceId; - -import android.app.Activity; -import android.content.Context; -import android.util.DisplayMetrics; -import android.view.LayoutInflater; -import android.view.ViewGroup; -import com.todoroo.astrid.service.TaskCompleter; -import javax.inject.Inject; -import org.tasks.R; -import org.tasks.dialogs.Linkify; -import org.tasks.injection.ForActivity; -import org.tasks.preferences.Preferences; -import org.tasks.ui.CheckBoxProvider; -import org.tasks.ui.ChipProvider; - -public class ViewHolderFactory { - - private final int textColorSecondary; - private final int textColorOverdue; - private final Context context; - private final ChipProvider chipProvider; - private final int fontSize; - private final CheckBoxProvider checkBoxProvider; - private final TaskCompleter taskCompleter; - private final DisplayMetrics metrics; - private final int background; - private final int selectedColor; - private final int rowPadding; - private final Linkify linkify; - private final Preferences preferences; - - @Inject - public ViewHolderFactory( - @ForActivity Context context, - Preferences preferences, - ChipProvider chipProvider, - CheckBoxProvider checkBoxProvider, - TaskCompleter taskCompleter, - Linkify linkify) { - this.context = context; - this.chipProvider = chipProvider; - this.checkBoxProvider = checkBoxProvider; - this.taskCompleter = taskCompleter; - this.preferences = preferences; - this.linkify = linkify; - textColorSecondary = getData(context, android.R.attr.textColorSecondary); - textColorOverdue = context.getColor(R.color.overdue); - background = getResourceId(context, R.attr.selectableItemBackground); - selectedColor = getData(context, R.attr.colorControlHighlight); - fontSize = preferences.getFontSize(); - metrics = context.getResources().getDisplayMetrics(); - rowPadding = convertDpToPixels(metrics, preferences.getInt(R.string.p_rowPadding, 16)); - } - - TaskViewHolder newViewHolder(ViewGroup parent, TaskViewHolder.ViewHolderCallbacks callbacks) { - return new TaskViewHolder( - (Activity) context, - (ViewGroup) - LayoutInflater.from(context).inflate(R.layout.task_adapter_row, parent, false), - preferences, - fontSize, - chipProvider, - checkBoxProvider, - textColorOverdue, - textColorSecondary, - taskCompleter, - callbacks, - metrics, - background, - selectedColor, - rowPadding, - linkify); - } -} diff --git a/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt b/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt new file mode 100644 index 000000000..5a4021b3a --- /dev/null +++ b/app/src/main/java/org/tasks/tasklist/ViewHolderFactory.kt @@ -0,0 +1,62 @@ +package org.tasks.tasklist + +import android.app.Activity +import android.content.Context +import android.util.DisplayMetrics +import android.view.LayoutInflater +import android.view.ViewGroup +import com.todoroo.andlib.utility.AndroidUtilities +import com.todoroo.astrid.service.TaskCompleter +import org.tasks.R +import org.tasks.dialogs.Linkify +import org.tasks.injection.ForActivity +import org.tasks.preferences.Preferences +import org.tasks.preferences.ResourceResolver +import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks +import org.tasks.ui.CheckBoxProvider +import org.tasks.ui.ChipProvider +import java.util.* +import javax.inject.Inject + +class ViewHolderFactory @Inject constructor( + @param:ForActivity private val context: Context, + private val preferences: Preferences, + private val chipProvider: ChipProvider, + private val checkBoxProvider: CheckBoxProvider, + private val taskCompleter: TaskCompleter, + private val linkify: Linkify, + private val locale: Locale) { + private val textColorSecondary: Int = ResourceResolver.getData(context, android.R.attr.textColorSecondary) + private val textColorOverdue: Int = context.getColor(R.color.overdue) + private val fontSize: Int = preferences.fontSize + private val metrics: DisplayMetrics = context.resources.displayMetrics + private val background: Int = ResourceResolver.getResourceId(context, R.attr.selectableItemBackground) + private val selectedColor: Int = ResourceResolver.getData(context, R.attr.colorControlHighlight) + private val rowPadding: Int = AndroidUtilities.convertDpToPixels(metrics, preferences.getInt(R.string.p_rowPadding, 16)) + + fun newHeaderViewHolder(parent: ViewGroup?, callback: (Long) -> Unit) = + HeaderViewHolder( + context, + locale, + LayoutInflater.from(context).inflate(R.layout.task_adapter_header, parent, false), + callback) + + fun newViewHolder(parent: ViewGroup?, callbacks: ViewHolderCallbacks?) = + TaskViewHolder( + context as Activity, + LayoutInflater.from(context).inflate(R.layout.task_adapter_row, parent, false) as ViewGroup, + preferences, + fontSize, + chipProvider, + checkBoxProvider, + textColorOverdue, + textColorSecondary, + taskCompleter, + callbacks, + metrics, + background, + selectedColor, + rowPadding, + linkify, + locale) +} \ No newline at end of file diff --git a/app/src/main/res/layout/task_adapter_header.xml b/app/src/main/res/layout/task_adapter_header.xml new file mode 100644 index 000000000..dc1d84a86 --- /dev/null +++ b/app/src/main/res/layout/task_adapter_header.xml @@ -0,0 +1,16 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 75a787997..54cc0e4e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -630,4 +630,6 @@ File %1$s contained %2$s.\n\n Unlock additional features and support open source software No thanks Got it! + Created %s + Modified %s