mirror of https://github.com/tasks/tasks
Collapsible sort group headers
parent
395cba2705
commit
8b2ed5b1e9
@ -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)
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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" />
|
||||
|
||||
Loading…
Reference in New Issue