Collapsible sort group headers

pull/996/head
Alex Baker 6 years ago
parent 395cba2705
commit 8b2ed5b1e9

@ -41,7 +41,7 @@ class CaldavManualSortTaskAdapterTest : InjectingTestCase() {
private val dataSource = object : TaskAdapterDataSource { private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position] override fun getItem(position: Int) = tasks[position]
override fun getItemCount() = tasks.size override fun getTaskCount() = tasks.size
} }
@Before @Before

@ -36,7 +36,7 @@ class CaldavTaskAdapterTest : InjectingTestCase() {
adapter.setDataSource(object : TaskAdapterDataSource { adapter.setDataSource(object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position] override fun getItem(position: Int) = tasks[position]
override fun getItemCount() = tasks.size override fun getTaskCount() = tasks.size
}) })
} }

@ -40,7 +40,7 @@ class GoogleTaskManualSortAdapterTest : InjectingTestCase() {
private val dataSource = object : TaskAdapterDataSource { private val dataSource = object : TaskAdapterDataSource {
override fun getItem(position: Int) = tasks[position] override fun getItem(position: Int) = tasks[position]
override fun getItemCount() = tasks.size override fun getTaskCount() = tasks.size
} }
@Test @Test

@ -404,6 +404,7 @@ class MainActivity : InjectingAppCompatActivity(), TaskListFragmentCallbackHandl
} }
override fun sortChanged(reload: Boolean) { override fun sortChanged(reload: Boolean) {
taskListFragment?.clearCollapsed()
localBroadcastManager.broadcastRefresh() localBroadcastManager.broadcastRefresh()
if (reload) { if (reload) {
openTaskListFragment(filter, true) openTaskListFragment(filter, true)

@ -167,7 +167,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
if (savedInstanceState != null) { if (savedInstanceState != null) {
val longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS) val longArray = savedInstanceState.getLongArray(EXTRA_SELECTED_TASK_IDS)
if (longArray?.isNotEmpty() == true) { if (longArray?.isNotEmpty() == true) {
taskAdapter.setSelected(*longArray) taskAdapter.setSelected(longArray.toList())
startActionMode() startActionMode()
} }
} }
@ -185,6 +185,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
val selectedTaskIds: List<Long> = taskAdapter.getSelected() val selectedTaskIds: List<Long> = taskAdapter.getSelected()
outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray()) outState.putLongArray(EXTRA_SELECTED_TASK_IDS, selectedTaskIds.toLongArray())
outState.putString(EXTRA_SEARCH, searchQuery) outState.putString(EXTRA_SEARCH, searchQuery)
outState.putLongArray(EXTRA_COLLAPSED, taskAdapter.getCollapsed().toLongArray())
} }
override fun onCreateView( override fun onCreateView(
@ -197,6 +198,7 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
// set up list adapters // set up list adapters
taskAdapter = taskAdapterProvider.createTaskAdapter(filter) taskAdapter = taskAdapterProvider.createTaskAdapter(filter)
taskAdapter.setCollapsed(savedInstanceState?.getLongArray(EXTRA_COLLAPSED))
taskListViewModel = ViewModelProvider(requireActivity()).get(TaskListViewModel::class.java) taskListViewModel = ViewModelProvider(requireActivity()).get(TaskListViewModel::class.java)
if (savedInstanceState != null) { if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(EXTRA_SEARCH) searchQuery = savedInstanceState.getString(EXTRA_SEARCH)
@ -231,13 +233,13 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
if (recyclerAdapter !is PagedListRecyclerAdapter) { if (recyclerAdapter !is PagedListRecyclerAdapter) {
setAdapter( setAdapter(
PagedListRecyclerAdapter( PagedListRecyclerAdapter(
taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao)) taskAdapter, recyclerView, viewHolderFactory, this, tasks, taskDao, preferences))
return return
} }
} else if (recyclerAdapter !is DragAndDropRecyclerAdapter) { } else if (recyclerAdapter !is DragAndDropRecyclerAdapter) {
setAdapter( setAdapter(
DragAndDropRecyclerAdapter( DragAndDropRecyclerAdapter(
taskAdapter, recyclerView, viewHolderFactory, this, tasks as MutableList, taskDao)) taskAdapter, recyclerView, viewHolderFactory, this, tasks as MutableList, taskDao, preferences))
return return
} }
recyclerAdapter!!.submitList(tasks) recyclerAdapter!!.submitList(tasks)
@ -740,6 +742,8 @@ class TaskListFragment : InjectingFragment(), OnRefreshListener, Toolbar.OnMenuI
makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString()).show() makeSnackbar(R.string.copy_multiple_tasks_confirmation, duplicates.size.toString()).show()
} }
fun clearCollapsed() = taskAdapter.clearCollapsed()
companion object { companion object {
const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$ const val TAGS_METADATA_JOIN = "for_tags" // $NON-NLS-1$
const val GTASK_METADATA_JOIN = "googletask" // $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" const val ACTION_DELETED = "action_deleted"
private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids" private const val EXTRA_SELECTED_TASK_IDS = "extra_selected_task_ids"
private const val EXTRA_SEARCH = "extra_search" 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 VOICE_RECOGNITION_REQUEST_CODE = 1234
private const val EXTRA_FILTER = "extra_filter" private const val EXTRA_FILTER = "extra_filter"
private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker" private const val FRAG_TAG_REMOTE_LIST_PICKER = "frag_tag_remote_list_picker"

@ -12,6 +12,9 @@ open class CaldavTaskAdapter internal constructor(private val taskDao: TaskDao,
override fun minIndent(nextPosition: Int, task: TaskContainer): Int { override fun minIndent(nextPosition: Int, task: TaskContainer): Int {
(nextPosition until count).forEach { (nextPosition until count).forEach {
if (isHeader(it)) {
return 0
}
if (!taskIsChild(task, it)) { if (!taskIsChild(task, it)) {
return getTask(it).indent 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 { private fun taskIsChild(source: TaskContainer, destinationIndex: Int): Boolean {
(destinationIndex downTo 0).forEach { (destinationIndex downTo 0).forEach {
if (isHeader(it)) {
return false
}
when (getTask(it).parent) { when (getTask(it).parent) {
0L -> return false 0L -> return false
source.parent -> return false source.parent -> return false

@ -11,10 +11,11 @@ import java.util.*
open class TaskAdapter { open class TaskAdapter {
private val selected = HashSet<Long>() private val selected = HashSet<Long>()
private val collapsed = HashSet<Long>()
private lateinit var dataSource: TaskAdapterDataSource private lateinit var dataSource: TaskAdapterDataSource
val count: Int val count: Int
get() = dataSource.getItemCount() get() = dataSource.getTaskCount()
fun setDataSource(dataSource: TaskAdapterDataSource) { fun setDataSource(dataSource: TaskAdapterDataSource) {
this.dataSource = dataSource this.dataSource = dataSource
@ -25,15 +26,22 @@ open class TaskAdapter {
fun getSelected(): ArrayList<Long> = ArrayList(selected) fun getSelected(): ArrayList<Long> = ArrayList(selected)
fun setSelected(vararg ids: Long) = setSelected(ids.toList())
fun setSelected(ids: Collection<Long>) { fun setSelected(ids: Collection<Long>) {
selected.clear() clearSelections()
selected.addAll(ids) selected.addAll(ids)
} }
fun clearSelections() = selected.clear() fun clearSelections() = selected.clear()
fun getCollapsed(): ArrayList<Long> = 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 getIndent(task: TaskContainer): Int = task.getIndent()
open fun canMove(source: TaskContainer, from: Int, target: TaskContainer, to: Int): Boolean = false 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 supportsParentingOrManualSort(): Boolean = false
open fun supportsManualSorting(): Boolean = false open fun supportsManualSorting(): Boolean = false
open fun moved(from: Int, to: Int, indent: Int) {} 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 getTask(position: Int): TaskContainer = dataSource.getItem(position)
fun getItemUuid(position: Int): String = getTask(position).uuid fun getItemUuid(position: Int): String = getTask(position).uuid

@ -5,5 +5,7 @@ import org.tasks.data.TaskContainer
interface TaskAdapterDataSource { interface TaskAdapterDataSource {
fun getItem(position: Int): TaskContainer fun getItem(position: Int): TaskContainer
fun getItemCount(): Int fun getTaskCount(): Int
fun isHeader(position: Int): Boolean = false
} }

@ -11,6 +11,7 @@ import static org.tasks.db.QueryUtils.showHidden;
import static org.tasks.db.QueryUtils.showRecentlyCompleted; import static org.tasks.db.QueryUtils.showRecentlyCompleted;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import androidx.annotation.Nullable;
import com.todoroo.andlib.sql.Functions; import com.todoroo.andlib.sql.Functions;
import com.todoroo.andlib.sql.Order; import com.todoroo.andlib.sql.Order;
import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Task;
@ -122,6 +123,21 @@ public class SortHelper {
return order; 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) { public static String orderSelectForSortTypeRecursive(int sortType) {
String select; String select;
switch (sortType) { switch (sortType) {

@ -11,6 +11,7 @@ public class TaskContainer {
@Embedded public Location location; @Embedded public Location location;
public String tags; public String tags;
public int children; public int children;
public Long sortGroup;
public long primarySort; public long primarySort;
public long secondarySort; public long secondarySort;
public int indent; public int indent;
@ -52,6 +53,10 @@ public class TaskContainer {
return task.hasDueDate(); return task.hasDueDate();
} }
public boolean hasDueTime() {
return task.hasDueTime();
}
public boolean isOverdue() { public boolean isOverdue() {
return task.isOverdue(); return task.isOverdue();
} }
@ -107,14 +112,24 @@ public class TaskContainer {
&& Objects.equals(googletask, that.googletask) && Objects.equals(googletask, that.googletask)
&& Objects.equals(caldavTask, that.caldavTask) && Objects.equals(caldavTask, that.caldavTask)
&& Objects.equals(location, that.location) && Objects.equals(location, that.location)
&& Objects.equals(tags, that.tags); && Objects.equals(tags, that.tags)
&& Objects.equals(sortGroup, that.sortGroup);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects return Objects.hash(
.hash(task, googletask, caldavTask, location, tags, children, primarySort, task,
secondarySort, indent, targetIndent); googletask,
caldavTask,
location,
tags,
children,
sortGroup,
primarySort,
secondarySort,
indent,
targetIndent);
} }
@Override @Override
@ -133,6 +148,8 @@ public class TaskContainer {
+ '\'' + '\''
+ ", children=" + ", children="
+ children + children
+ ", sortGroup="
+ sortGroup
+ ", primarySort=" + ", primarySort="
+ primarySort + primarySort
+ ", secondarySort=" + ", secondarySort="

@ -22,6 +22,7 @@ internal object TaskListQueryRecursive {
TaskListQuery.FIELDS.plus(listOf( 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("(${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("indent"),
field("sort_group").`as`("sortGroup"),
field("children"), field("children"),
field("primary_sort").`as`("primarySort"), field("primary_sort").`as`("primarySort"),
field("secondary_sort").`as`("secondarySort"))).toTypedArray() field("secondary_sort").`as`("secondarySort"))).toTypedArray()
@ -94,10 +95,10 @@ internal object TaskListQueryRecursive {
val sortSelect = SortHelper.orderSelectForSortTypeRecursive(sortMode) val sortSelect = SortHelper.orderSelectForSortTypeRecursive(sortMode)
val withClause =""" val withClause ="""
CREATE TEMPORARY TABLE `recursive_tasks` AS CREATE TEMPORARY TABLE `recursive_tasks` AS
WITH RECURSIVE recursive_tasks (task, parent, collapsed, hidden, indent, title, sortField, primary_sort, secondary_sort) AS ( 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 FROM tasks 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 $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 $subtaskQuery
ORDER BY sort_indent DESC, ${SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)} ORDER BY sort_indent DESC, ${SortHelper.orderForSortTypeRecursive(sortMode, reverseSort)}
) SELECT * FROM recursive_tasks ) SELECT * FROM recursive_tasks

@ -23,6 +23,10 @@ class ApplicationModule(@get:Provides @get:ForApplication val context: Context)
val locale: Locale val locale: Locale
get() = Locale.getInstance(context) get() = Locale.getInstance(context)
@Provides
@ApplicationScope
fun getJavaLocale(locale: Locale): java.util.Locale = locale.locale
@Provides @Provides
@ApplicationScope @ApplicationScope
fun getNotificationDao(db: Database): NotificationDao = db.notificationDao() fun getNotificationDao(db: Database): NotificationDao = db.notificationDao()

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

@ -2,21 +2,32 @@ package org.tasks.tasklist
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapter
import org.tasks.data.TaskContainer
internal class DiffCallback(private val old: List<TaskContainer>, private val new: List<TaskContainer>, @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 getOldListSize() = old.size
override fun getNewListSize() = new.size override fun getNewListSize() = new.size
override fun areItemsTheSame(oldPosition: Int, newPosition: Int): Boolean { 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 { override fun areContentsTheSame(oldPosition: Int, newPosition: Int): Boolean {
val oldItem = old[oldPosition] if (new.isHeader(newPosition)) {
val newItem = new[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) return oldItem == newItem && oldItem.getIndent() == adapter.getIndent(newItem)
} }
} }

@ -1,6 +1,7 @@
package org.tasks.tasklist package org.tasks.tasklist
import android.graphics.Canvas import android.graphics.Canvas
import android.view.ViewGroup
import androidx.core.util.Pair import androidx.core.util.Pair
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper 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.adapter.TaskAdapter
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.utility.Flags import com.todoroo.astrid.utility.Flags
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject import io.reactivex.subjects.PublishSubject
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences
import java.util.* import java.util.*
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -25,27 +28,61 @@ class DragAndDropRecyclerAdapter(
private val recyclerView: RecyclerView, private val recyclerView: RecyclerView,
viewHolderFactory: ViewHolderFactory, viewHolderFactory: ViewHolderFactory,
private val taskList: TaskListFragment, private val taskList: TaskListFragment,
private var list: MutableList<TaskContainer>, tasks: MutableList<TaskContainer>,
taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { taskDao: TaskDao,
private val publishSubject = PublishSubject.create<MutableList<TaskContainer>>() preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) {
private var list: SectionedDataSource
private val publishSubject = PublishSubject.create<SectionedDataSource>()
private val disposables = CompositeDisposable() private val disposables = CompositeDisposable()
private val updates: Queue<Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>> = LinkedList() private val updates: Queue<Pair<SectionedDataSource, DiffUtil.DiffResult>> = LinkedList()
private var dragging = false 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 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<TaskContainer>) = publishSubject.onNext(list as MutableList<TaskContainer>) override fun submitList(list: List<TaskContainer>) {
disposables.add(
Single.fromCallable { SectionedDataSource(list as MutableList, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet()) }
.subscribeOn(Schedulers.computation())
.subscribe(publishSubject::onNext))
}
private fun calculateDiff( private fun calculateDiff(
last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: MutableList<TaskContainer>): Pair<MutableList<TaskContainer>, DiffUtil.DiffResult> { last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource): Pair<SectionedDataSource, DiffUtil.DiffResult> {
AndroidUtilities.assertNotMainThread() AndroidUtilities.assertNotMainThread()
val cb = DiffCallback(last.first!!, next, adapter) val cb = DiffCallback(last.first!!, next, adapter)
val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE) val result = DiffUtil.calculateDiff(cb, next.size < LONG_LIST_SIZE)
return Pair.create(next, result) return Pair.create(next, result)
} }
private fun applyDiff(update: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>) { private fun applyDiff(update: Pair<SectionedDataSource, DiffUtil.DiffResult>) {
AndroidUtilities.assertMainThread() AndroidUtilities.assertMainThread()
updates.add(update) updates.add(update)
if (!dragging) { if (!dragging) {
@ -67,9 +104,9 @@ class DragAndDropRecyclerAdapter(
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose() override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) = disposables.dispose()
override fun getItemCount(): Int { override fun getTaskCount() = list.taskCount
return list.size
} override fun getItemCount() = list.size
private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() { private inner class ItemTouchHelperCallback : ItemTouchHelper.Callback() {
private var from = -1 private var from = -1
@ -85,10 +122,14 @@ class DragAndDropRecyclerAdapter(
} }
} }
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) = if (adapter.supportsParentingOrManualSort() && adapter.numSelected == 0) { override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 0) return if (adapter.isHeader(viewHolder.adapterPosition)) {
} else { makeMovementFlags(0, 0)
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( override fun onMove(
@ -99,7 +140,8 @@ class DragAndDropRecyclerAdapter(
val fromPosition = src.adapterPosition val fromPosition = src.adapterPosition
val toPosition = target.adapterPosition val toPosition = target.adapterPosition
val source = src as TaskViewHolder 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 return false
} }
if (from == -1) { if (from == -1) {
@ -108,14 +150,19 @@ class DragAndDropRecyclerAdapter(
} }
to = toPosition to = toPosition
notifyItemMoved(fromPosition, toPosition) notifyItemMoved(fromPosition, toPosition)
if (isHeader) {
val offset = if (fromPosition < toPosition) -1 else 1
list.moveSection(toPosition, offset)
}
updateIndents(source, from, to) updateIndents(source, from, to)
return true return true
} }
private fun updateIndents(source: TaskViewHolder?, from: Int, to: Int) { private fun updateIndents(source: TaskViewHolder?, from: Int, to: Int) {
val task = source!!.task val task = source!!.task
source.minIndent = if (to == 0 || to == itemCount - 1) 0 else adapter.minIndent(if (from <= to) to + 1 else to, task) val previousIsHeader = to > 0 && isHeader(to - 1)
source.maxIndent = if (to == 0) 0 else adapter.maxIndent(if (from >= to) to - 1 else to, task) 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( override fun onChildDraw(
@ -191,7 +238,14 @@ class DragAndDropRecyclerAdapter(
throw UnsupportedOperationException() 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) adapter.moved(from, to, indent)
val task: TaskContainer = list.removeAt(from) val task: TaskContainer = list.removeAt(from)
list.add(if (from < to) to - 1 else to, task) list.add(if (from < to) to - 1 else to, task)
@ -204,16 +258,18 @@ class DragAndDropRecyclerAdapter(
} }
init { init {
val filter = taskList.getFilter()
disableHeaders = !filter.supportsSorting() || preferences.isManualSort && filter.supportsManualSort()
ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView) ItemTouchHelper(ItemTouchHelperCallback()).attachToRecyclerView(recyclerView)
val initial = Pair.create<MutableList<TaskContainer>, DiffUtil.DiffResult>(list, null) list = SectionedDataSource(tasks, disableHeaders, preferences.sortMode, adapter.getCollapsed().toMutableSet())
disposables.add( val initial = Pair.create<SectionedDataSource, DiffUtil.DiffResult>(list, null)
publishSubject disposables.add(publishSubject
.observeOn(Schedulers.computation()) .observeOn(Schedulers.computation())
.scan(initial, { last: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult>, next: List<TaskContainer> -> .scan(initial, { last: Pair<SectionedDataSource, DiffUtil.DiffResult>, next: SectionedDataSource ->
calculateDiff(last, next.toMutableList()) calculateDiff(last, next)
}) })
.skip(1) .skip(1)
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe { update: Pair<MutableList<TaskContainer>, DiffUtil.DiffResult> -> applyDiff(update) }) .subscribe { update: Pair<SectionedDataSource, DiffUtil.DiffResult> -> applyDiff(update) })
} }
} }

@ -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)
}
}
}

@ -8,6 +8,7 @@ import com.todoroo.astrid.activity.TaskListFragment
import com.todoroo.astrid.adapter.TaskAdapter import com.todoroo.astrid.adapter.TaskAdapter
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.preferences.Preferences
class PagedListRecyclerAdapter( class PagedListRecyclerAdapter(
adapter: TaskAdapter, adapter: TaskAdapter,
@ -15,7 +16,8 @@ class PagedListRecyclerAdapter(
viewHolderFactory: ViewHolderFactory, viewHolderFactory: ViewHolderFactory,
taskList: TaskListFragment, taskList: TaskListFragment,
list: List<TaskContainer>, list: List<TaskContainer>,
taskDao: TaskDao) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao) { taskDao: TaskDao,
preferences: Preferences) : TaskListRecyclerAdapter(adapter, viewHolderFactory, taskList, taskDao, preferences) {
private val differ: AsyncPagedListDiffer<TaskContainer> = private val differ: AsyncPagedListDiffer<TaskContainer> =
AsyncPagedListDiffer(this, AsyncDifferConfig.Builder(ItemCallback()).build()) AsyncPagedListDiffer(this, AsyncDifferConfig.Builder(ItemCallback()).build())
@ -34,6 +36,8 @@ class PagedListRecyclerAdapter(
override fun getItemCount() = differ.itemCount override fun getItemCount() = differ.itemCount
override fun getTaskCount() = itemCount
init { init {
if (list is PagedList<*>) { if (list is PagedList<*>) {
differ.submitList(list as PagedList<TaskContainer>?) differ.submitList(list as PagedList<TaskContainer>?)

@ -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<TaskContainer>, disableHeaders: Boolean, val sortMode: Int, private val collapsed: MutableSet<Long>) {
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<AdapterSection> {
val sections = ArrayList<AdapterSection>()
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<AdapterSection>): SparseArray<AdapterSection> {
val sections = SparseArray<AdapterSection>()
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)
}
}

@ -10,22 +10,24 @@ import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import org.tasks.data.TaskContainer import org.tasks.data.TaskContainer
import org.tasks.intents.TaskIntents import org.tasks.intents.TaskIntents
import org.tasks.preferences.Preferences
import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks import org.tasks.tasklist.TaskViewHolder.ViewHolderCallbacks
abstract class TaskListRecyclerAdapter internal constructor( abstract class TaskListRecyclerAdapter internal constructor(
private val adapter: TaskAdapter, private val adapter: TaskAdapter,
private val viewHolderFactory: ViewHolderFactory, internal val viewHolderFactory: ViewHolderFactory,
private val taskList: TaskListFragment, private val taskList: TaskListFragment,
private val taskDao: TaskDao) : RecyclerView.Adapter<TaskViewHolder>(), ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource { private val taskDao: TaskDao,
internal val preferences: Preferences)
: RecyclerView.Adapter<RecyclerView.ViewHolder>(), ViewHolderCallbacks, ListUpdateCallback, TaskAdapterDataSource {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TaskViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = viewHolderFactory.newViewHolder(parent, this)
return 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) val task = getItem(position)
if (task != null) { if (task != null) {
holder.bindView(task, taskList.getFilter()) (holder as TaskViewHolder).bindView(task, taskList.getFilter(), sortMode)
holder.isMoving = false holder.isMoving = false
val indent = adapter.getIndent(task) val indent = adapter.getIndent(task)
task.setIndent(indent) task.setIndent(indent)
@ -85,8 +87,6 @@ abstract class TaskListRecyclerAdapter internal constructor(
protected abstract fun dragAndDropEnabled(): Boolean protected abstract fun dragAndDropEnabled(): Boolean
abstract override fun getItem(position: Int): TaskContainer
abstract fun submitList(list: List<TaskContainer>) abstract fun submitList(list: List<TaskContainer>)
override fun onInserted(position: Int, count: Int) { override fun onInserted(position: Int, count: Int) {

@ -1,6 +1,8 @@
package org.tasks.tasklist; package org.tasks.tasklist;
import static com.todoroo.andlib.utility.DateUtilities.getRelativeDateTime; 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.annotation.SuppressLint;
import android.app.Activity; import android.app.Activity;
@ -18,13 +20,14 @@ import butterknife.OnLongClick;
import com.google.android.material.chip.Chip; import com.google.android.material.chip.Chip;
import com.google.android.material.chip.ChipGroup; import com.google.android.material.chip.ChipGroup;
import com.todoroo.astrid.api.Filter; import com.todoroo.astrid.api.Filter;
import com.todoroo.astrid.core.SortHelper;
import com.todoroo.astrid.service.TaskCompleter; import com.todoroo.astrid.service.TaskCompleter;
import com.todoroo.astrid.ui.CheckableImageView; import com.todoroo.astrid.ui.CheckableImageView;
import java.util.List; import java.util.List;
import java.util.Locale;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.TaskContainer; import org.tasks.data.TaskContainer;
import org.tasks.dialogs.Linkify; import org.tasks.dialogs.Linkify;
import org.tasks.locale.Locale;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.ui.CheckBoxProvider; import org.tasks.ui.CheckBoxProvider;
import org.tasks.ui.ChipProvider; import org.tasks.ui.ChipProvider;
@ -42,6 +45,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
private final int selectedColor; private final int selectedColor;
private final int rowPadding; private final int rowPadding;
private final Linkify linkify; private final Linkify linkify;
private final Locale locale;
private final CheckBoxProvider checkBoxProvider; private final CheckBoxProvider checkBoxProvider;
private final int textColorOverdue; private final int textColorOverdue;
private final ChipProvider chipProvider; private final ChipProvider chipProvider;
@ -93,7 +97,8 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
int background, int background,
int selectedColor, int selectedColor,
int rowPadding, int rowPadding,
Linkify linkify) { Linkify linkify,
Locale locale) {
super(view); super(view);
this.context = context; this.context = context;
this.preferences = preferences; this.preferences = preferences;
@ -108,6 +113,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
this.selectedColor = selectedColor; this.selectedColor = selectedColor;
this.rowPadding = rowPadding; this.rowPadding = rowPadding;
this.linkify = linkify; this.linkify = linkify;
this.locale = locale;
ButterKnife.bind(this, view); ButterKnife.bind(this, view);
if (preferences.getBoolean(R.string.p_fullTaskTitle, false)) { if (preferences.getBoolean(R.string.p_fullTaskTitle, false)) {
@ -188,14 +194,14 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
return Math.round(indent * getShiftSize()); return Math.round(indent * getShiftSize());
} }
void bindView(TaskContainer task, Filter filter) { void bindView(TaskContainer task, Filter filter, int sortMode) {
this.task = task; this.task = task;
this.indent = task.indent; this.indent = task.indent;
nameView.setText(task.getTitle()); nameView.setText(task.getTitle());
hiddenIcon.setVisibility(task.isHidden() ? View.VISIBLE : View.GONE); hiddenIcon.setVisibility(task.isHidden() ? View.VISIBLE : View.GONE);
setupTitleAndCheckbox(); setupTitleAndCheckbox();
setupDueDate(); setupDueDate(sortMode);
setupChips(filter); setupChips(filter);
if (preferences.getBoolean(R.string.p_show_description, true)) { if (preferences.getBoolean(R.string.p_show_description, true)) {
description.setText(task.getNotes()); description.setText(task.getNotes());
@ -233,16 +239,20 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
completeBox.invalidate(); completeBox.invalidate();
} }
private void setupDueDate() { private void setupDueDate(int sortMode) {
if (task.hasDueDate()) { if (task.hasDueDate()) {
if (task.isOverdue()) { if (task.isOverdue()) {
dueDate.setTextColor(textColorOverdue); dueDate.setTextColor(textColorOverdue);
} else { } else {
dueDate.setTextColor(textColorSecondary); dueDate.setTextColor(textColorSecondary);
} }
String dateValue = String dateValue;
getRelativeDateTime( if (sortMode == SortHelper.SORT_DUE && task.sortGroup != null && newDateTime(task.sortGroup).startOfDay().equals(newDateTime(task.getDueDate()).startOfDay())) {
context, task.getDueDate(), Locale.getInstance().getLocale(), FormatStyle.MEDIUM); dateValue =
task.hasDueTime() ? getTimeString(context, newDateTime(task.getDueDate())) : null;
} else {
dateValue = getRelativeDateTime(context, task.getDueDate(), locale, FormatStyle.MEDIUM);
}
dueDate.setText(dateValue); dueDate.setText(dateValue);
dueDate.setVisibility(View.VISIBLE); dueDate.setVisibility(View.VISIBLE);
} else { } else {
@ -332,7 +342,7 @@ public class TaskViewHolder extends RecyclerView.ViewHolder {
return maxIndent; return maxIndent;
} }
interface ViewHolderCallbacks { public interface ViewHolderCallbacks {
void onCompletedTask(TaskContainer task, boolean newState); void onCompletedTask(TaskContainer task, boolean newState);

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

@ -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)
}

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/header"
style="@style/TextAppearance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:padding="@dimen/keyline_first"
android:gravity="start|center_vertical"
android:textColor="@color/text_secondary"
android:textSize="@dimen/sku_details_row_text_size"
android:drawableEnd="@drawable/ic_keyboard_arrow_down_black_18dp"
android:drawableTint="@color/text_tertiary"
app:fontFamily="sans-serif-medium" />

@ -630,4 +630,6 @@ File %1$s contained %2$s.\n\n
<string name="support_development_subscribe">Unlock additional features and support open source software</string> <string name="support_development_subscribe">Unlock additional features and support open source software</string>
<string name="no_thanks">No thanks</string> <string name="no_thanks">No thanks</string>
<string name="got_it">Got it!</string> <string name="got_it">Got it!</string>
<string name="sort_created_group">Created %s</string>
<string name="sort_modified_group">Modified %s</string>
</resources> </resources>

Loading…
Cancel
Save