mirror of https://github.com/tasks/tasks
Refactor widget
parent
4d1d6a06a8
commit
f33cc896dd
@ -0,0 +1,37 @@
|
||||
package org.tasks.extensions
|
||||
|
||||
import android.graphics.Paint.ANTI_ALIAS_FLAG
|
||||
import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
|
||||
import android.widget.RemoteViews
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import org.tasks.R
|
||||
|
||||
fun RemoteViews.setColorFilter(viewId: Int, @ColorInt color: Int) =
|
||||
setInt(viewId, "setColorFilter", color)
|
||||
|
||||
fun RemoteViews.setBackgroundColor(viewId: Int, color: Int, opacity: Int) =
|
||||
setInt(viewId, "setBackgroundColor", ColorUtils.setAlphaComponent(color, opacity))
|
||||
|
||||
fun RemoteViews.setBackgroundResource(viewId: Int, @DrawableRes resId: Int) =
|
||||
setInt(viewId, "setBackgroundResource", resId)
|
||||
|
||||
fun RemoteViews.setTextSize(viewId: Int, size: Float) =
|
||||
setFloat(viewId, "setTextSize", size)
|
||||
|
||||
fun RemoteViews.strikethrough(viewId: Int, strikethrough: Boolean) =
|
||||
setInt(
|
||||
viewId,
|
||||
"setPaintFlags",
|
||||
if (strikethrough) STRIKE_THRU_TEXT_FLAG or ANTI_ALIAS_FLAG else ANTI_ALIAS_FLAG
|
||||
)
|
||||
|
||||
fun RemoteViews.setMaxLines(viewId: Int, maxLines: Int) =
|
||||
setInt(viewId, "setMaxLines", maxLines)
|
||||
|
||||
fun RemoteViews.setRipple(viewId: Int, dark: Boolean) =
|
||||
setBackgroundResource(
|
||||
viewId,
|
||||
if (dark) R.drawable.widget_ripple_circle_light else R.drawable.widget_ripple_circle_dark
|
||||
)
|
@ -1,395 +0,0 @@
|
||||
package org.tasks.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Paint
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import com.todoroo.andlib.utility.DateUtilities
|
||||
import com.todoroo.andlib.utility.DateUtilities.now
|
||||
import com.todoroo.astrid.api.AstridOrderingFilter
|
||||
import com.todoroo.astrid.api.Filter
|
||||
import com.todoroo.astrid.core.SortHelper
|
||||
import com.todoroo.astrid.data.Task
|
||||
import com.todoroo.astrid.subtasks.SubtasksHelper
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.tasks.BuildConfig
|
||||
import org.tasks.LocalBroadcastManager
|
||||
import org.tasks.R
|
||||
import org.tasks.data.TaskContainer
|
||||
import org.tasks.data.TaskDao
|
||||
import org.tasks.data.TaskListQuery.getQuery
|
||||
import org.tasks.date.DateTimeUtils
|
||||
import org.tasks.extensions.Context.isNightMode
|
||||
import org.tasks.markdown.Markdown
|
||||
import org.tasks.preferences.DefaultFilterProvider
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.tasklist.HeaderFormatter
|
||||
import org.tasks.tasklist.SectionedDataSource
|
||||
import org.tasks.tasklist.SectionedDataSource.Companion.HEADER_COMPLETED
|
||||
import org.tasks.time.DateTimeUtils.startOfDay
|
||||
import org.tasks.ui.CheckBoxProvider
|
||||
import timber.log.Timber
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
internal class ScrollableViewsFactory(
|
||||
private val subtasksHelper: SubtasksHelper,
|
||||
preferences: Preferences,
|
||||
private val context: Context,
|
||||
private val widgetId: Int,
|
||||
private val taskDao: TaskDao,
|
||||
private val defaultFilterProvider: DefaultFilterProvider,
|
||||
private val checkBoxProvider: CheckBoxProvider,
|
||||
private val locale: Locale,
|
||||
private val chipProvider: ChipProvider,
|
||||
private val localBroadcastManager: LocalBroadcastManager,
|
||||
private val markdown: Markdown,
|
||||
private val headerFormatter: HeaderFormatter,
|
||||
) : RemoteViewsFactory {
|
||||
private val indentPadding: Int
|
||||
private var showDueDates = false
|
||||
private var endDueDate = false
|
||||
private var showCheckboxes = false
|
||||
private var textSize = 0f
|
||||
private var dueDateTextSize = 0f
|
||||
private var filter: Filter? = null
|
||||
private var textColorPrimary = 0
|
||||
private var textColorSecondary = 0
|
||||
private var showFullTaskTitle = false
|
||||
private var showDescription = false
|
||||
private var showFullDescription = false
|
||||
private var vPad = 0
|
||||
private var hPad = 0
|
||||
private var handleDueDateClick = false
|
||||
private var showDividers = false
|
||||
private var disableGroups = false
|
||||
private var showSubtasks = false
|
||||
private var showStartDates = false
|
||||
private var showPlaces = false
|
||||
private var showLists = false
|
||||
private var showTags = false
|
||||
private var collapsed = mutableSetOf(HEADER_COMPLETED)
|
||||
private var groupMode = -1
|
||||
private var subtaskMode = -1
|
||||
private var tasks = SectionedDataSource(
|
||||
emptyList(),
|
||||
disableHeaders = false,
|
||||
groupMode = SortHelper.GROUP_NONE,
|
||||
subtaskMode = SortHelper.SORT_MANUAL,
|
||||
collapsed,
|
||||
preferences.completedTasksAtBottom,
|
||||
)
|
||||
private val widgetPreferences = WidgetPreferences(context, preferences, widgetId)
|
||||
private var isDark = checkIfDark
|
||||
private var showFullDate = false
|
||||
private var compact = false
|
||||
|
||||
private val checkIfDark: Boolean
|
||||
get() = when (widgetPreferences.themeIndex) {
|
||||
0 -> false
|
||||
3 -> context.isNightMode
|
||||
else -> true
|
||||
}
|
||||
|
||||
override fun onCreate() {}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
runBlocking {
|
||||
updateSettings()
|
||||
tasks = SectionedDataSource(
|
||||
taskDao.fetchTasks { getQuery(filter) },
|
||||
disableGroups,
|
||||
groupMode,
|
||||
subtaskMode,
|
||||
collapsed,
|
||||
widgetPreferences.completedTasksAtBottom,
|
||||
)
|
||||
if (collapsed.retainAll(tasks.getSectionValues())) {
|
||||
widgetPreferences.collapsed = collapsed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {}
|
||||
|
||||
override fun getCount(): Int {
|
||||
if (isDark != checkIfDark) {
|
||||
isDark = !isDark
|
||||
localBroadcastManager.reconfigureWidget(widgetId)
|
||||
}
|
||||
return tasks.size
|
||||
}
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews? =
|
||||
if (tasks.isHeader(position)) buildHeader(position) else buildUpdate(position)
|
||||
|
||||
override fun getLoadingView(): RemoteViews = newRemoteView()
|
||||
|
||||
override fun getViewTypeCount(): Int = 2
|
||||
|
||||
override fun getItemId(position: Int) = getTask(position)?.id ?: 0
|
||||
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
private fun getCheckbox(task: Task): Bitmap = checkBoxProvider.getWidgetCheckBox(task)
|
||||
|
||||
private fun newRemoteView(): RemoteViews = RemoteViews(
|
||||
BuildConfig.APPLICATION_ID,
|
||||
if (isDark) R.layout.widget_row_dark else R.layout.widget_row_light
|
||||
)
|
||||
|
||||
private fun buildHeader(position: Int): RemoteViews {
|
||||
val row = RemoteViews(
|
||||
BuildConfig.APPLICATION_ID,
|
||||
if (isDark) R.layout.widget_header_dark else R.layout.widget_header_light
|
||||
)
|
||||
val section = tasks.getSection(position)
|
||||
val sortGroup = section.value
|
||||
val header: String? = if (filter?.supportsSorting() == true) {
|
||||
headerFormatter.headerStringBlocking(
|
||||
value = section.value,
|
||||
groupMode = groupMode,
|
||||
alwaysDisplayFullDate = showFullDate,
|
||||
style = FormatStyle.MEDIUM,
|
||||
compact = compact,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
row.setTextViewText(R.id.header, header)
|
||||
row.setImageViewResource(R.id.arrow, if (section.collapsed) {
|
||||
R.drawable.ic_keyboard_arrow_down_black_18dp
|
||||
} else {
|
||||
R.drawable.ic_keyboard_arrow_up_black_18dp
|
||||
})
|
||||
row.setTextColor(
|
||||
R.id.header,
|
||||
section.headerColor(
|
||||
context,
|
||||
groupMode,
|
||||
if (isDark) R.color.white_60 else R.color.black_60
|
||||
)
|
||||
)
|
||||
if (!showDividers) {
|
||||
row.setViewVisibility(R.id.divider, View.GONE)
|
||||
}
|
||||
row.setOnClickFillInIntent(
|
||||
R.id.row,
|
||||
Intent(WidgetClickActivity.TOGGLE_GROUP)
|
||||
.putExtra(WidgetClickActivity.EXTRA_WIDGET, widgetId)
|
||||
.putExtra(WidgetClickActivity.EXTRA_GROUP, sortGroup)
|
||||
.putExtra(WidgetClickActivity.EXTRA_COLLAPSED, !section.collapsed)
|
||||
)
|
||||
return row
|
||||
}
|
||||
|
||||
private fun buildUpdate(position: Int): RemoteViews? {
|
||||
try {
|
||||
val taskContainer = getTask(position) ?: return null
|
||||
val task = taskContainer.task
|
||||
var textColorTitle = textColorPrimary
|
||||
val row = newRemoteView()
|
||||
if (task.isHidden) {
|
||||
textColorTitle = textColorSecondary
|
||||
}
|
||||
if (task.isCompleted) {
|
||||
textColorTitle = textColorSecondary
|
||||
row.setInt(
|
||||
R.id.widget_text, "setPaintFlags", Paint.STRIKE_THRU_TEXT_FLAG or Paint.ANTI_ALIAS_FLAG)
|
||||
} else {
|
||||
row.setInt(R.id.widget_text, "setPaintFlags", Paint.ANTI_ALIAS_FLAG)
|
||||
}
|
||||
row.setFloat(R.id.widget_text, "setTextSize", textSize)
|
||||
if (showDueDates) {
|
||||
formatDueDate(row, taskContainer)
|
||||
} else {
|
||||
row.setViewVisibility(R.id.widget_due_bottom, View.GONE)
|
||||
row.setViewVisibility(R.id.widget_due_end, View.GONE)
|
||||
if (task.hasDueDate() && task.isOverdue) {
|
||||
textColorTitle = context.getColor(R.color.overdue)
|
||||
}
|
||||
}
|
||||
if (showFullTaskTitle) {
|
||||
row.setInt(R.id.widget_text, "setMaxLines", Int.MAX_VALUE)
|
||||
}
|
||||
row.setTextViewText(
|
||||
R.id.widget_text,
|
||||
markdown.toMarkdown(task.title)
|
||||
)
|
||||
row.setTextColor(R.id.widget_text, textColorTitle)
|
||||
if (showDescription && task.hasNotes()) {
|
||||
row.setFloat(R.id.widget_description, "setTextSize", textSize)
|
||||
row.setTextViewText(
|
||||
R.id.widget_description,
|
||||
markdown.toMarkdown(task.notes)
|
||||
)
|
||||
row.setViewVisibility(R.id.widget_description, View.VISIBLE)
|
||||
if (showFullDescription) {
|
||||
row.setInt(R.id.widget_description, "setMaxLines", Int.MAX_VALUE)
|
||||
}
|
||||
} else {
|
||||
row.setViewVisibility(R.id.widget_description, View.GONE)
|
||||
}
|
||||
row.setOnClickFillInIntent(
|
||||
R.id.widget_row,
|
||||
Intent(WidgetClickActivity.EDIT_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_FILTER, filter)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task))
|
||||
if (showCheckboxes) {
|
||||
row.setViewPadding(R.id.widget_complete_box, hPad, vPad, hPad, vPad)
|
||||
row.setImageViewBitmap(R.id.widget_complete_box, getCheckbox(task))
|
||||
row.setOnClickFillInIntent(
|
||||
R.id.widget_complete_box,
|
||||
Intent(WidgetClickActivity.COMPLETE_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task))
|
||||
} else {
|
||||
row.setViewPadding(R.id.widget_complete_box, hPad, 0, 0, 0)
|
||||
row.setInt(R.id.widget_complete_box, "setBackgroundResource", 0)
|
||||
}
|
||||
row.setViewPadding(R.id.top_padding, 0, vPad, 0, 0)
|
||||
row.setViewPadding(R.id.bottom_padding, 0, vPad, 0, 0)
|
||||
if (!showDividers) {
|
||||
row.setViewVisibility(R.id.divider, View.GONE)
|
||||
}
|
||||
row.removeAllViews(R.id.chips)
|
||||
if (showSubtasks && taskContainer.hasChildren()) {
|
||||
val chip = chipProvider.getSubtaskChip(taskContainer)
|
||||
row.addView(R.id.chips, chip)
|
||||
row.setOnClickFillInIntent(
|
||||
R.id.chip,
|
||||
Intent(WidgetClickActivity.TOGGLE_SUBTASKS)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task)
|
||||
.putExtra(WidgetClickActivity.EXTRA_COLLAPSED, !taskContainer.isCollapsed)
|
||||
)
|
||||
}
|
||||
if (taskContainer.isHidden && showStartDates) {
|
||||
val sortByDate = groupMode == SortHelper.SORT_START && !disableGroups
|
||||
chipProvider
|
||||
.getStartDateChip(taskContainer, showFullDate, sortByDate)
|
||||
?.let { row.addView(R.id.chips, it) }
|
||||
}
|
||||
if (taskContainer.hasLocation() && showPlaces) {
|
||||
chipProvider
|
||||
.getPlaceChip(filter, taskContainer)
|
||||
?.let { row.addView(R.id.chips, it) }
|
||||
}
|
||||
if (!taskContainer.hasParent() && showLists) {
|
||||
chipProvider
|
||||
.getListChip(filter, taskContainer)
|
||||
?.let { row.addView(R.id.chips, it) }
|
||||
}
|
||||
if (showTags && taskContainer.tagsString?.isNotBlank() == true) {
|
||||
chipProvider
|
||||
.getTagChips(filter, taskContainer)
|
||||
.forEach { row.addView(R.id.chips, it) }
|
||||
}
|
||||
val startPad = taskContainer.indent * indentPadding
|
||||
row.setViewPadding(R.id.widget_row, startPad, 0, 0, 0)
|
||||
return row
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getTask(position: Int): TaskContainer? = tasks.getItem(position)
|
||||
|
||||
private suspend fun getQuery(filter: Filter?): List<String> {
|
||||
val queries = getQuery(widgetPreferences, filter!!)
|
||||
val last = queries.size - 1
|
||||
queries[last] =
|
||||
subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences, queries[last])
|
||||
return queries
|
||||
}
|
||||
|
||||
private fun formatDueDate(row: RemoteViews, task: TaskContainer) {
|
||||
val dueDateRes = if (endDueDate) R.id.widget_due_end else R.id.widget_due_bottom
|
||||
row.setViewVisibility(if (endDueDate) R.id.widget_due_bottom else R.id.widget_due_end, View.GONE)
|
||||
val hasDueDate = task.hasDueDate()
|
||||
val endPad = if (hasDueDate && endDueDate) 0 else hPad
|
||||
row.setViewPadding(R.id.widget_text, 0, 0, endPad, 0)
|
||||
if (hasDueDate) {
|
||||
if (endDueDate) {
|
||||
row.setViewPadding(R.id.widget_due_end, hPad, vPad, hPad, vPad)
|
||||
}
|
||||
row.setViewVisibility(dueDateRes, View.VISIBLE)
|
||||
val text = if (
|
||||
groupMode == SortHelper.SORT_DUE &&
|
||||
(task.sortGroup ?: 0L) >= now().startOfDay() &&
|
||||
!disableGroups
|
||||
) {
|
||||
task.takeIf { it.hasDueTime() }?.let {
|
||||
DateUtilities.getTimeString(context, DateTimeUtils.newDateTime(task.dueDate))
|
||||
}
|
||||
} else {
|
||||
DateUtilities.getRelativeDateTime(
|
||||
context, task.dueDate, locale, FormatStyle.MEDIUM, showFullDate, false)
|
||||
}
|
||||
row.setTextViewText(dueDateRes, text)
|
||||
row.setTextColor(
|
||||
dueDateRes,
|
||||
if (task.isOverdue) context.getColor(R.color.overdue) else textColorSecondary)
|
||||
row.setFloat(dueDateRes, "setTextSize", dueDateTextSize)
|
||||
if (handleDueDateClick) {
|
||||
row.setOnClickFillInIntent(
|
||||
dueDateRes,
|
||||
Intent(WidgetClickActivity.RESCHEDULE_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task.task))
|
||||
} else {
|
||||
row.setInt(dueDateRes, "setBackgroundResource", 0)
|
||||
}
|
||||
} else {
|
||||
row.setViewVisibility(dueDateRes, View.GONE)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateSettings() {
|
||||
vPad = widgetPreferences.widgetSpacing
|
||||
hPad = context.resources.getDimension(R.dimen.widget_padding).toInt()
|
||||
handleDueDateClick = widgetPreferences.rescheduleOnDueDateClick()
|
||||
showFullTaskTitle = widgetPreferences.showFullTaskTitle()
|
||||
showDescription = widgetPreferences.showDescription()
|
||||
showFullDescription = widgetPreferences.showFullDescription()
|
||||
chipProvider.isDark = isDark
|
||||
textColorPrimary = context.getColor(if (isDark) R.color.white_87 else R.color.black_87)
|
||||
textColorSecondary = context.getColor(if (isDark) R.color.white_60 else R.color.black_60)
|
||||
val dueDatePosition = widgetPreferences.dueDatePosition
|
||||
showDueDates = dueDatePosition != 2
|
||||
endDueDate = dueDatePosition != 1
|
||||
showCheckboxes = widgetPreferences.showCheckboxes()
|
||||
textSize = widgetPreferences.fontSize.toFloat()
|
||||
dueDateTextSize = max(10f, textSize - 2)
|
||||
filter = defaultFilterProvider.getFilterFromPreference(widgetPreferences.filterId)
|
||||
showDividers = widgetPreferences.showDividers()
|
||||
disableGroups = filter?.let {
|
||||
!it.supportsSorting()
|
||||
|| (it.supportsManualSort() && widgetPreferences.isManualSort)
|
||||
|| (it is AstridOrderingFilter && widgetPreferences.isAstridSort)
|
||||
} == true
|
||||
showPlaces = widgetPreferences.showPlaces()
|
||||
showSubtasks = widgetPreferences.showSubtasks()
|
||||
showStartDates = widgetPreferences.showStartDates()
|
||||
showLists = widgetPreferences.showLists()
|
||||
showTags = widgetPreferences.showTags()
|
||||
showFullDate = widgetPreferences.alwaysDisplayFullDate
|
||||
widgetPreferences.groupMode.takeIf { it != groupMode }
|
||||
?.let {
|
||||
if (groupMode != SortHelper.GROUP_NONE) {
|
||||
widgetPreferences.collapsed = mutableSetOf(HEADER_COMPLETED)
|
||||
}
|
||||
groupMode = it
|
||||
}
|
||||
subtaskMode = widgetPreferences.subtaskMode
|
||||
collapsed = widgetPreferences.collapsed
|
||||
compact = widgetPreferences.compact
|
||||
}
|
||||
|
||||
init {
|
||||
val metrics = context.resources.displayMetrics
|
||||
indentPadding = (20 * metrics.density).toInt()
|
||||
}
|
||||
}
|
@ -1,74 +0,0 @@
|
||||
package org.tasks.widget;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.widget.RemoteViewsService;
|
||||
|
||||
import com.todoroo.astrid.subtasks.SubtasksHelper;
|
||||
|
||||
import org.tasks.LocalBroadcastManager;
|
||||
import org.tasks.data.TaskDao;
|
||||
import org.tasks.markdown.MarkdownProvider;
|
||||
import org.tasks.preferences.DefaultFilterProvider;
|
||||
import org.tasks.preferences.Preferences;
|
||||
import org.tasks.tasklist.HeaderFormatter;
|
||||
import org.tasks.themes.ColorProvider;
|
||||
import org.tasks.ui.CheckBoxProvider;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import dagger.hilt.android.AndroidEntryPoint;
|
||||
|
||||
@AndroidEntryPoint
|
||||
public class ScrollableWidgetUpdateService extends RemoteViewsService {
|
||||
|
||||
@Inject TaskDao taskDao;
|
||||
@Inject Preferences preferences;
|
||||
@Inject SubtasksHelper subtasksHelper;
|
||||
@Inject DefaultFilterProvider defaultFilterProvider;
|
||||
@Inject Locale locale;
|
||||
@Inject ChipProvider chipProvider;
|
||||
@Inject LocalBroadcastManager localBroadcastManager;
|
||||
@Inject MarkdownProvider markdownProvider;
|
||||
@Inject HeaderFormatter headerFormatter;
|
||||
|
||||
@Override
|
||||
public void onStart(Intent intent, int startId) {
|
||||
super.onStart(intent, startId);
|
||||
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RemoteViewsFactory onGetViewFactory(Intent intent) {
|
||||
if (intent == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
if (extras == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int widgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID);
|
||||
Context context = getApplicationContext();
|
||||
return new ScrollableViewsFactory(
|
||||
subtasksHelper,
|
||||
preferences,
|
||||
context,
|
||||
widgetId,
|
||||
taskDao,
|
||||
defaultFilterProvider,
|
||||
new CheckBoxProvider(context, new ColorProvider(context, preferences)),
|
||||
locale,
|
||||
chipProvider,
|
||||
localBroadcastManager,
|
||||
markdownProvider.markdown(false),
|
||||
headerFormatter
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package org.tasks.widget
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Intent
|
||||
import android.widget.RemoteViewsService
|
||||
import androidx.core.content.IntentCompat
|
||||
import com.todoroo.astrid.api.Filter
|
||||
import com.todoroo.astrid.subtasks.SubtasksHelper
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import org.tasks.data.TaskDao
|
||||
import org.tasks.markdown.MarkdownProvider
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.tasklist.HeaderFormatter
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TasksWidgetAdapter : RemoteViewsService() {
|
||||
@Inject lateinit var taskDao: TaskDao
|
||||
@Inject lateinit var preferences: Preferences
|
||||
@Inject lateinit var subtasksHelper: SubtasksHelper
|
||||
@Inject lateinit var locale: Locale
|
||||
@Inject lateinit var chipProvider: WidgetChipProvider
|
||||
@Inject lateinit var markdownProvider: MarkdownProvider
|
||||
@Inject lateinit var headerFormatter: HeaderFormatter
|
||||
|
||||
override fun onGetViewFactory(intent: Intent): RemoteViewsFactory? {
|
||||
val widgetId = intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID) ?: return null
|
||||
val filter = IntentCompat.getParcelableExtra(intent, EXTRA_FILTER, Filter::class.java) ?: return null
|
||||
return TasksWidgetViewFactory(
|
||||
subtasksHelper,
|
||||
preferences,
|
||||
filter,
|
||||
applicationContext,
|
||||
widgetId,
|
||||
taskDao,
|
||||
locale,
|
||||
chipProvider,
|
||||
markdownProvider.markdown(false),
|
||||
headerFormatter,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_FILTER = "extra_filter"
|
||||
}
|
||||
}
|
@ -0,0 +1,321 @@
|
||||
package org.tasks.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.widget.RemoteViews
|
||||
import android.widget.RemoteViewsService.RemoteViewsFactory
|
||||
import com.todoroo.andlib.utility.DateUtilities
|
||||
import com.todoroo.andlib.utility.DateUtilities.now
|
||||
import com.todoroo.astrid.api.AstridOrderingFilter
|
||||
import com.todoroo.astrid.api.Filter
|
||||
import com.todoroo.astrid.core.SortHelper
|
||||
import com.todoroo.astrid.subtasks.SubtasksHelper
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.tasks.BuildConfig
|
||||
import org.tasks.R
|
||||
import org.tasks.data.TaskContainer
|
||||
import org.tasks.data.TaskDao
|
||||
import org.tasks.data.TaskListQuery.getQuery
|
||||
import org.tasks.date.DateTimeUtils
|
||||
import org.tasks.extensions.Context.isNightMode
|
||||
import org.tasks.extensions.setBackgroundResource
|
||||
import org.tasks.extensions.setColorFilter
|
||||
import org.tasks.extensions.setMaxLines
|
||||
import org.tasks.extensions.setTextSize
|
||||
import org.tasks.extensions.strikethrough
|
||||
import org.tasks.markdown.Markdown
|
||||
import org.tasks.preferences.Preferences
|
||||
import org.tasks.tasklist.HeaderFormatter
|
||||
import org.tasks.tasklist.SectionedDataSource
|
||||
import org.tasks.themes.ColorProvider.Companion.priorityColor
|
||||
import org.tasks.time.DateTimeUtils.startOfDay
|
||||
import org.tasks.ui.CheckBoxProvider.Companion.getCheckboxRes
|
||||
import timber.log.Timber
|
||||
import java.time.format.FormatStyle
|
||||
import java.util.Locale
|
||||
import kotlin.math.max
|
||||
|
||||
internal class TasksWidgetViewFactory(
|
||||
private val subtasksHelper: SubtasksHelper,
|
||||
preferences: Preferences,
|
||||
private val filter: Filter,
|
||||
private val context: Context,
|
||||
private val widgetId: Int,
|
||||
private val taskDao: TaskDao,
|
||||
private val locale: Locale,
|
||||
private val chipProvider: WidgetChipProvider,
|
||||
private val markdown: Markdown,
|
||||
private val headerFormatter: HeaderFormatter,
|
||||
) : RemoteViewsFactory {
|
||||
private val indentPadding = (20 * context.resources.displayMetrics.density).toInt()
|
||||
private val widgetPreferences = WidgetPreferences(context, preferences, widgetId)
|
||||
private val settings = widgetPreferences.getWidgetListSettings()
|
||||
private val hPad = context.resources.getDimension(R.dimen.widget_padding).toInt()
|
||||
private val disableGroups = !filter.supportsSorting()
|
||||
|| (filter.supportsManualSort() && widgetPreferences.isManualSort)
|
||||
|| (filter is AstridOrderingFilter && widgetPreferences.isAstridSort)
|
||||
private var tasks = SectionedDataSource()
|
||||
private val isDark = when (widgetPreferences.themeIndex) {
|
||||
0 -> false
|
||||
3 -> context.isNightMode
|
||||
else -> true
|
||||
}
|
||||
private val onSurface = context.getColor(if (isDark) R.color.white_87 else R.color.black_87)
|
||||
private val onSurfaceVariant = context.getColor(if (isDark) R.color.white_60 else R.color.black_60)
|
||||
|
||||
init {
|
||||
chipProvider.isDark = isDark
|
||||
}
|
||||
|
||||
override fun onCreate() {}
|
||||
|
||||
override fun onDataSetChanged() {
|
||||
runBlocking {
|
||||
val collapsed = widgetPreferences.collapsed
|
||||
tasks = SectionedDataSource(
|
||||
taskDao.fetchTasks { getQuery(filter) },
|
||||
disableGroups,
|
||||
settings.groupMode,
|
||||
widgetPreferences.subtaskMode,
|
||||
collapsed,
|
||||
widgetPreferences.completedTasksAtBottom,
|
||||
)
|
||||
collapsed.toMutableSet().let {
|
||||
if (it.retainAll(tasks.getSectionValues().toSet())) {
|
||||
widgetPreferences.collapsed = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {}
|
||||
|
||||
override fun getCount() = tasks.size
|
||||
|
||||
override fun getViewAt(position: Int): RemoteViews? =
|
||||
if (tasks.isHeader(position)) buildHeader(position) else buildUpdate(position)
|
||||
|
||||
override fun getLoadingView(): RemoteViews = newRemoteView()
|
||||
|
||||
override fun getViewTypeCount(): Int = 2
|
||||
|
||||
override fun getItemId(position: Int) = getTask(position)?.id ?: 0
|
||||
|
||||
override fun hasStableIds(): Boolean = true
|
||||
|
||||
private fun newRemoteView() = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_row)
|
||||
|
||||
private fun buildHeader(position: Int): RemoteViews {
|
||||
val section = tasks.getSection(position)
|
||||
val sortGroup = section.value
|
||||
val header: String? = if (filter.supportsSorting()) {
|
||||
headerFormatter.headerStringBlocking(
|
||||
value = section.value,
|
||||
groupMode = settings.groupMode,
|
||||
alwaysDisplayFullDate = settings.showFullDate,
|
||||
style = FormatStyle.MEDIUM,
|
||||
compact = settings.compact,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
return RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_header).apply {
|
||||
setTextViewText(R.id.header, header)
|
||||
setImageViewResource(
|
||||
R.id.arrow, if (section.collapsed) {
|
||||
R.drawable.ic_keyboard_arrow_down_black_18dp
|
||||
} else {
|
||||
R.drawable.ic_keyboard_arrow_up_black_18dp
|
||||
}
|
||||
)
|
||||
setTextColor(
|
||||
R.id.header,
|
||||
section.headerColor(
|
||||
context,
|
||||
settings.groupMode,
|
||||
if (isDark) R.color.white_60 else R.color.black_60
|
||||
)
|
||||
)
|
||||
if (!settings.showDividers) {
|
||||
setViewVisibility(R.id.divider, View.GONE)
|
||||
}
|
||||
setOnClickFillInIntent(
|
||||
R.id.row,
|
||||
Intent(WidgetClickActivity.TOGGLE_GROUP)
|
||||
.putExtra(WidgetClickActivity.EXTRA_WIDGET, widgetId)
|
||||
.putExtra(WidgetClickActivity.EXTRA_GROUP, sortGroup)
|
||||
.putExtra(WidgetClickActivity.EXTRA_COLLAPSED, !section.collapsed)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildUpdate(position: Int): RemoteViews? {
|
||||
return try {
|
||||
val taskContainer = getTask(position) ?: return null
|
||||
val task = taskContainer.task
|
||||
val textColorTitle = when {
|
||||
task.isHidden -> onSurfaceVariant
|
||||
task.isCompleted -> onSurfaceVariant
|
||||
!settings.showDueDates && task.isOverdue -> context.getColor(R.color.overdue)
|
||||
else -> onSurface
|
||||
}
|
||||
newRemoteView().apply {
|
||||
strikethrough(R.id.widget_text, task.isCompleted)
|
||||
setTextSize(R.id.widget_text, settings.textSize)
|
||||
if (settings.showDueDates) {
|
||||
formatDueDate(this, taskContainer)
|
||||
} else {
|
||||
setViewVisibility(R.id.widget_due_bottom, View.GONE)
|
||||
setViewVisibility(R.id.widget_due_end, View.GONE)
|
||||
}
|
||||
if (settings.showFullTaskTitle) {
|
||||
setMaxLines(R.id.widget_text, Int.MAX_VALUE)
|
||||
}
|
||||
setTextViewText(
|
||||
R.id.widget_text,
|
||||
markdown.toMarkdown(task.title)
|
||||
)
|
||||
setTextColor(R.id.widget_text, textColorTitle)
|
||||
if (settings.showDescription && task.hasNotes()) {
|
||||
setTextSize(R.id.widget_description, settings.textSize)
|
||||
setTextColor(R.id.widget_description, onSurfaceVariant)
|
||||
setTextViewText(
|
||||
R.id.widget_description,
|
||||
markdown.toMarkdown(task.notes)
|
||||
)
|
||||
setViewVisibility(R.id.widget_description, View.VISIBLE)
|
||||
if (settings.showFullDescription) {
|
||||
setMaxLines(R.id.widget_description, Int.MAX_VALUE)
|
||||
}
|
||||
} else {
|
||||
setViewVisibility(R.id.widget_description, View.GONE)
|
||||
}
|
||||
setOnClickFillInIntent(
|
||||
R.id.widget_row,
|
||||
Intent(WidgetClickActivity.EDIT_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_FILTER, filter)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task)
|
||||
)
|
||||
if (settings.showCheckboxes) {
|
||||
setViewPadding(
|
||||
R.id.widget_complete_box,
|
||||
hPad,
|
||||
settings.vPad,
|
||||
hPad,
|
||||
settings.vPad
|
||||
)
|
||||
setImageViewResource(R.id.widget_complete_box, task.getCheckboxRes())
|
||||
setColorFilter(R.id.widget_complete_box, priorityColor(task.priority))
|
||||
setOnClickFillInIntent(
|
||||
R.id.widget_complete_box,
|
||||
Intent(WidgetClickActivity.COMPLETE_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task)
|
||||
)
|
||||
} else {
|
||||
setViewPadding(R.id.widget_complete_box, hPad, 0, 0, 0)
|
||||
setBackgroundResource(R.id.widget_complete_box, 0)
|
||||
}
|
||||
setViewPadding(R.id.top_padding, 0, settings.vPad, 0, 0)
|
||||
setViewPadding(R.id.bottom_padding, 0, settings.vPad, 0, 0)
|
||||
if (!settings.showDividers) {
|
||||
setViewVisibility(R.id.divider, View.GONE)
|
||||
}
|
||||
removeAllViews(R.id.chips)
|
||||
if (settings.showSubtaskChips && taskContainer.hasChildren()) {
|
||||
val chip = chipProvider.getSubtaskChip(taskContainer)
|
||||
addView(R.id.chips, chip)
|
||||
setOnClickFillInIntent(
|
||||
R.id.chip,
|
||||
Intent(WidgetClickActivity.TOGGLE_SUBTASKS)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task)
|
||||
.putExtra(
|
||||
WidgetClickActivity.EXTRA_COLLAPSED,
|
||||
!taskContainer.isCollapsed
|
||||
)
|
||||
)
|
||||
}
|
||||
if (taskContainer.isHidden && settings.showStartChips) {
|
||||
val sortByDate = settings.groupMode == SortHelper.SORT_START && !disableGroups
|
||||
chipProvider
|
||||
.getStartDateChip(taskContainer, settings.showFullDate, sortByDate)
|
||||
?.let { addView(R.id.chips, it) }
|
||||
}
|
||||
if (taskContainer.hasLocation() && settings.showPlaceChips) {
|
||||
chipProvider
|
||||
.getPlaceChip(filter, taskContainer)
|
||||
?.let { addView(R.id.chips, it) }
|
||||
}
|
||||
if (!taskContainer.hasParent() && settings.showListChips) {
|
||||
chipProvider
|
||||
.getListChip(filter, taskContainer)
|
||||
?.let { addView(R.id.chips, it) }
|
||||
}
|
||||
if (settings.showTagChips && taskContainer.tagsString?.isNotBlank() == true) {
|
||||
chipProvider
|
||||
.getTagChips(filter, taskContainer)
|
||||
.forEach { addView(R.id.chips, it) }
|
||||
}
|
||||
val startPad = taskContainer.indent * indentPadding
|
||||
setViewPadding(R.id.widget_row, startPad, 0, 0, 0)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getTask(position: Int): TaskContainer? = tasks.getItem(position)
|
||||
|
||||
private suspend fun getQuery(filter: Filter): List<String> {
|
||||
val queries = getQuery(widgetPreferences, filter)
|
||||
val last = queries.size - 1
|
||||
queries[last] =
|
||||
subtasksHelper.applySubtasksToWidgetFilter(filter, widgetPreferences, queries[last])
|
||||
return queries
|
||||
}
|
||||
|
||||
private fun formatDueDate(row: RemoteViews, task: TaskContainer) = with(row) {
|
||||
val dueDateRes = if (settings.endDueDate) R.id.widget_due_end else R.id.widget_due_bottom
|
||||
setViewVisibility(
|
||||
if (settings.endDueDate) R.id.widget_due_bottom else R.id.widget_due_end,
|
||||
View.GONE
|
||||
)
|
||||
val hasDueDate = task.hasDueDate()
|
||||
val endPad = if (hasDueDate && settings.endDueDate) 0 else hPad
|
||||
setViewPadding(R.id.widget_text, 0, 0, endPad, 0)
|
||||
if (hasDueDate) {
|
||||
if (settings.endDueDate) {
|
||||
setViewPadding(R.id.widget_due_end, hPad, settings.vPad, hPad, settings.vPad)
|
||||
}
|
||||
setViewVisibility(dueDateRes, View.VISIBLE)
|
||||
val text = if (
|
||||
settings.groupMode == SortHelper.SORT_DUE &&
|
||||
(task.sortGroup ?: 0L) >= now().startOfDay() &&
|
||||
!disableGroups
|
||||
) {
|
||||
task.takeIf { it.hasDueTime() }?.let {
|
||||
DateUtilities.getTimeString(context, DateTimeUtils.newDateTime(task.dueDate))
|
||||
}
|
||||
} else {
|
||||
DateUtilities.getRelativeDateTime(
|
||||
context, task.dueDate, locale, FormatStyle.MEDIUM, settings.showFullDate, false
|
||||
)
|
||||
}
|
||||
setTextViewText(dueDateRes, text)
|
||||
setTextColor(
|
||||
dueDateRes,
|
||||
if (task.isOverdue) context.getColor(R.color.overdue) else onSurfaceVariant
|
||||
)
|
||||
setTextSize(dueDateRes, max(10f, settings.textSize - 2))
|
||||
setOnClickFillInIntent(
|
||||
dueDateRes,
|
||||
Intent(WidgetClickActivity.RESCHEDULE_TASK)
|
||||
.putExtra(WidgetClickActivity.EXTRA_TASK, task.task)
|
||||
)
|
||||
} else {
|
||||
setViewVisibility(dueDateRes, View.GONE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<corners android:radius="12dp" />
|
||||
<stroke android:width="@dimen/chip_stroke" android:color="@color/black_60" />
|
||||
<solid android:color="@android:color/transparent"/>
|
||||
</shape>
|
@ -1,49 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/chip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/chip_icon"
|
||||
android:layout_width="@dimen/chip_icon_size"
|
||||
android:layout_height="@dimen/chip_icon_size"
|
||||
android:layout_alignTop="@id/chip_text"
|
||||
android:layout_alignBottom="@id/chip_text"
|
||||
android:paddingStart="5dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:tint="@color/icon_tint_light_alpha"
|
||||
tools:src="@drawable/ic_keyboard_arrow_up_black_24dp"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chip_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_toEndOf="@id/chip_icon"
|
||||
android:textSize="14sp"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:paddingBottom="2dp"
|
||||
android:paddingStart="2dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:textColor="@color/black_60"
|
||||
tools:text="4 subtasks"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/chip_background"
|
||||
android:src="@drawable/widget_chip_light_bg"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignBottom="@id/chip_text"
|
||||
android:layout_alignEnd="@id/chip_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</RelativeLayout>
|
@ -1,51 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/row"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
tools:background="@color/md_background_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/arrow"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/half_keyline_first"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="@dimen/keyline_first"
|
||||
android:paddingBottom="@dimen/half_keyline_first"
|
||||
tools:src="@drawable/ic_keyboard_arrow_down_black_18dp"
|
||||
android:tint="@color/icon_tint_dark_alpha"
|
||||
android:layout_alignParentEnd="true"
|
||||
tools:ignore="UseAppTint" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/header"
|
||||
style="@style/TextAppearance"
|
||||
android:paddingTop="@dimen/half_keyline_first"
|
||||
android:paddingBottom="@dimen/half_keyline_first"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toStartOf="@id/arrow"
|
||||
android:paddingStart="@dimen/keyline_first"
|
||||
android:paddingEnd="0dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textSize="@dimen/sku_details_row_text_size"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
tools:textColor="@color/white_60"
|
||||
tools:text="Today"
|
||||
app:fontFamily="sans-serif-medium" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/divider"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_below="@id/header"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_height=".5dp"
|
||||
android:background="@color/white_12"
|
||||
tools:ignore="ContentDescription" />
|
||||
</RelativeLayout>
|
@ -1,123 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/widget_row"
|
||||
android:background="@drawable/widget_ripple_dark"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:background="@color/md_background_dark">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_complete_box"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:padding="@dimen/widget_padding"
|
||||
android:background="@drawable/widget_ripple_circle_dark"
|
||||
android:soundEffectsEnabled="false"
|
||||
tools:src="@drawable/ic_outline_check_box_outline_blank_24px"
|
||||
tools:tint="@color/grey_500"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/top_padding"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignStart="@id/widget_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/widget_padding"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_due_end"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@id/widget_text"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/widget_ripple_circle_dark"
|
||||
tools:text="Tomorrow"
|
||||
tools:paddingEnd="@dimen/widget_padding"
|
||||
tools:textColor="@color/white_60"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_text"
|
||||
android:layout_toEndOf="@id/widget_complete_box"
|
||||
android:layout_toStartOf="@id/widget_due_end"
|
||||
android:layout_below="@id/top_padding"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingEnd="@dimen/widget_padding"
|
||||
android:paddingStart="0dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
tools:text="Task title"
|
||||
tools:textColor="@color/white_87"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/widget_text"
|
||||
android:layout_toEndOf="@id/widget_complete_box"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="@dimen/widget_padding"
|
||||
android:maxLines="2"
|
||||
android:ellipsize="end"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="16sp"
|
||||
android:textColor="@color/white_60"
|
||||
tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean tristique magna mauris, vel vulputate elit varius sit amet. Etiam sed nisl diam. Aenean vulputate ex nec ex condimentum commodo. Nunc faucibus augue in lacus tincidunt pretium. Donec mollis ex a ipsum semper, faucibus viverra turpis consequat. Donec id suscipit est. Duis a consectetur justo. Nunc in diam urna." />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_due_bottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/widget_description"
|
||||
android:layout_toEndOf="@id/widget_complete_box"
|
||||
android:ellipsize="end"
|
||||
android:gravity="start|center_vertical"
|
||||
android:singleLine="true"
|
||||
android:textAlignment="viewStart"
|
||||
android:textSize="14sp"
|
||||
android:background="@drawable/widget_ripple_circle_dark"
|
||||
tools:text="Tomorrow"
|
||||
tools:textColor="@color/white_60"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chips"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/widget_due_bottom"
|
||||
android:layout_toEndOf="@id/widget_complete_box"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="@dimen/widget_padding"
|
||||
android:orientation="horizontal" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/bottom_padding"
|
||||
android:layout_below="@id/chips"
|
||||
android:layout_alignStart="@id/widget_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/widget_padding"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/divider"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_width="match_parent"
|
||||
android:scaleType="fitXY"
|
||||
android:layout_height=".5dp"
|
||||
android:background="@color/white_12"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</RelativeLayout>
|
Loading…
Reference in New Issue