New drawer

pull/2584/head
Alex Baker 2 years ago
parent 7fd5647cb8
commit b5748aa8e6

@ -207,6 +207,7 @@ dependencies {
debugImplementation(libs.kotlin.reflect) debugImplementation(libs.kotlin.reflect)
implementation(libs.kotlin.jdk8) implementation(libs.kotlin.jdk8)
implementation(libs.kotlin.immutable)
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.persistent.cookiejar) implementation(libs.persistent.cookiejar)
implementation(libs.gson) implementation(libs.gson)

@ -8,12 +8,23 @@ package com.todoroo.astrid.activity
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.ActionMode import androidx.appcompat.view.ActionMode
import androidx.compose.material.MaterialTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment
import com.todoroo.astrid.activity.TaskListFragment.TaskListFragmentCallbackHandler import com.todoroo.astrid.activity.TaskListFragment.TaskListFragmentCallbackHandler
import com.todoroo.astrid.adapter.SubheaderClickHandler
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
@ -29,20 +40,32 @@ import kotlinx.coroutines.withContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity
import org.tasks.compose.collectAsStateLifecycleAware
import org.tasks.compose.drawer.DrawerAction
import org.tasks.compose.drawer.DrawerItem
import org.tasks.compose.drawer.TaskListDrawer
import org.tasks.data.AlarmDao import org.tasks.data.AlarmDao
import org.tasks.data.LocationDao import org.tasks.data.LocationDao
import org.tasks.data.Place import org.tasks.data.Place
import org.tasks.data.TagDataDao import org.tasks.data.TagDataDao
import org.tasks.databinding.TaskListActivityBinding import org.tasks.databinding.TaskListActivityBinding
import org.tasks.dialogs.NewFilterDialog
import org.tasks.dialogs.WhatsNewDialog import org.tasks.dialogs.WhatsNewDialog
import org.tasks.extensions.Context.nightMode import org.tasks.extensions.Context.nightMode
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.hideKeyboard import org.tasks.extensions.hideKeyboard
import org.tasks.filters.FilterProvider
import org.tasks.filters.PlaceFilter import org.tasks.filters.PlaceFilter
import org.tasks.intents.TaskIntents.getTaskListIntent import org.tasks.intents.TaskIntents.getTaskListIntent
import org.tasks.location.LocationPickerActivity import org.tasks.location.LocationPickerActivity
import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
import org.tasks.themes.Theme import org.tasks.themes.Theme
@ -50,8 +73,6 @@ import org.tasks.themes.ThemeColor
import org.tasks.ui.EmptyTaskEditFragment.Companion.newEmptyTaskEditFragment import org.tasks.ui.EmptyTaskEditFragment.Companion.newEmptyTaskEditFragment
import org.tasks.ui.MainActivityEvent import org.tasks.ui.MainActivityEvent
import org.tasks.ui.MainActivityEventBus import org.tasks.ui.MainActivityEventBus
import org.tasks.ui.NavigationDrawerFragment
import org.tasks.ui.NavigationDrawerFragment.Companion.newNavigationDrawer
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -71,14 +92,23 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
@Inject lateinit var eventBus: MainActivityEventBus @Inject lateinit var eventBus: MainActivityEventBus
@Inject lateinit var firebase: Firebase @Inject lateinit var firebase: Firebase
private val viewModel: MainActivityViewModel by viewModels()
private var currentNightMode = 0 private var currentNightMode = 0
private var currentPro = false private var currentPro = false
private var filter: Filter? = null
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private lateinit var binding: TaskListActivityBinding private lateinit var binding: TaskListActivityBinding
private val filter: Filter?
get() = viewModel.state.value.filter
private val settingsRequest =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
recreate()
}
/** @see android.app.Activity.onCreate /** @see android.app.Activity.onCreate
*/ */
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
theme.applyTheme(this) theme.applyTheme(this)
@ -86,15 +116,112 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
currentPro = inventory.hasPro currentPro = inventory.hasPro
binding = TaskListActivityBinding.inflate(layoutInflater) binding = TaskListActivityBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
if (savedInstanceState != null) {
filter = savedInstanceState.getParcelable(EXTRA_FILTER)
applyTheme()
}
handleIntent() handleIntent()
binding.composeView.setContent {
val state = viewModel.state.collectAsStateLifecycleAware().value
if (state.drawerOpen) {
MdcTheme {
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = preferences.isTopAppBar,
)
ModalBottomSheet(
sheetState = sheetState,
containerColor = MaterialTheme.colors.surface,
onDismissRequest = { viewModel.setDrawerOpen(false) }
) {
val scope = rememberCoroutineScope()
TaskListDrawer(
begForMoney = state.begForMoney,
filters = state.drawerItems,
onClick = {
when (it) {
is DrawerItem.Filter -> {
openTaskListFragment(it.type())
scope.launch(Dispatchers.Default) {
sheetState.hide()
viewModel.setDrawerOpen(false)
}
}
is DrawerItem.Header -> {
viewModel.toggleCollapsed(it.type())
}
}
},
onAddClick = {
scope.launch(Dispatchers.Default) {
sheetState.hide()
viewModel.setDrawerOpen(false)
val subheaderType = it.type()
val rc = subheaderType.addIntentRc
if (rc == FilterProvider.REQUEST_NEW_FILTER) {
NewFilterDialog.newFilterDialog().show(
supportFragmentManager,
SubheaderClickHandler.FRAG_TAG_NEW_FILTER
)
} else {
val intent = subheaderType.addIntent ?: return@launch
startActivityForResult(intent, rc)
}
}
},
onDrawerAction = {
viewModel.setDrawerOpen(false)
when (it) {
DrawerAction.PURCHASE ->
if (IS_GENERIC)
openUri(R.string.url_donate)
else
startActivity(
Intent(
this@MainActivity,
PurchaseActivity::class.java
)
)
DrawerAction.CUSTOMIZE_DRAWER ->
startActivity(
Intent(
this@MainActivity,
NavigationDrawerCustomization::class.java
)
)
DrawerAction.SETTINGS ->
settingsRequest.launch(
Intent(
this@MainActivity,
MainPreferences::class.java
)
)
DrawerAction.HELP_AND_FEEDBACK ->
startActivity(
Intent(
this@MainActivity,
HelpAndFeedback::class.java
)
)
}
},
onErrorClick = {
startActivity(Intent(this@MainActivity, MainPreferences::class.java))
},
)
}
}
}
}
eventBus eventBus
.onEach(this::process) .onEach(this::process)
.launchIn(lifecycleScope) .launchIn(lifecycleScope)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
applyTheme()
}
}
} }
private suspend fun process(event: MainActivityEvent) = when (event) { private suspend fun process(event: MainActivityEvent) = when (event) {
@ -106,14 +233,13 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) { when (requestCode) {
NavigationDrawerFragment.REQUEST_SETTINGS -> recreate() REQUEST_NEW_LIST ->
NavigationDrawerFragment.REQUEST_NEW_LIST ->
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
data data
?.getParcelableExtra<Filter>(OPEN_FILTER) ?.getParcelableExtra<Filter>(OPEN_FILTER)
?.let { startActivity(getTaskListIntent(this, it)) } ?.let { startActivity(getTaskListIntent(this, it)) }
} }
NavigationDrawerFragment.REQUEST_NEW_PLACE -> REQUEST_NEW_PLACE ->
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
data data
?.getParcelableExtra<Place>(LocationPickerActivity.EXTRA_PLACE) ?.getParcelableExtra<Place>(LocationPickerActivity.EXTRA_PLACE)
@ -130,14 +256,9 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
handleIntent() handleIntent()
} }
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(EXTRA_FILTER, filter)
}
private fun clearUi() { private fun clearUi() {
finishActionMode() finishActionMode()
navigationDrawer?.dismiss() viewModel.setDrawerOpen(false)
} }
private suspend fun getTaskToLoad(filter: Filter?): Task? { private suspend fun getTaskToLoad(filter: Filter?): Task? {
@ -274,8 +395,10 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
} }
private fun setFilter(newFilter: Filter?) { private fun setFilter(newFilter: Filter?) {
filter = newFilter newFilter?.let {
applyTheme() viewModel.setFilter(it)
applyTheme()
}
} }
private fun openTaskListFragment(filter: Filter?, force: Boolean = false) { private fun openTaskListFragment(filter: Filter?, force: Boolean = false) {
@ -291,8 +414,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
if (!force && filter == newFilter) { if (!force && filter == newFilter) {
return return
} }
filter = newFilter viewModel.setFilter(newFilter)
defaultFilterProvider.lastViewedFilter = newFilter
applyTheme() applyTheme()
supportFragmentManager supportFragmentManager
.beginTransaction() .beginTransaction()
@ -308,10 +430,8 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
} }
private val filterColor: ThemeColor private val filterColor: ThemeColor
get() = if (filter != null && filter!!.tint != 0) colorProvider.getThemeColor(filter!!.tint, true) else theme.themeColor get() = filter?.tint?.takeIf { it != 0 }
?.let { colorProvider.getThemeColor(it, true) } ?: theme.themeColor
private val navigationDrawer: NavigationDrawerFragment?
get() = supportFragmentManager.findFragmentByTag(FRAG_TAG_NAV_DRAWER) as? NavigationDrawerFragment
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
@ -362,7 +482,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
override fun onNavigationIconClicked() { override fun onNavigationIconClicked() {
hideKeyboard() hideKeyboard()
newNavigationDrawer(filter).show(supportFragmentManager, FRAG_TAG_NAV_DRAWER) viewModel.setDrawerOpen(true)
} }
private val taskListFragment: TaskListFragment? private val taskListFragment: TaskListFragment?
@ -413,10 +533,10 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
const val FINISH_AFFINITY = "finish_affinity" const val FINISH_AFFINITY = "finish_affinity"
private const val FRAG_TAG_TASK_LIST = "frag_tag_task_list" private const val FRAG_TAG_TASK_LIST = "frag_tag_task_list"
private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new" private const val FRAG_TAG_WHATS_NEW = "frag_tag_whats_new"
private const val FRAG_TAG_NAV_DRAWER = "frag_tag_nav_drawer"
private const val EXTRA_FILTER = "extra_filter"
private const val FLAG_FROM_HISTORY private const val FLAG_FROM_HISTORY
= Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
const val REQUEST_NEW_LIST = 10100
const val REQUEST_NEW_PLACE = 10104
val Intent.getFilter: Filter? val Intent.getFilter: Filter?
get() = if (isFromHistory) { get() = if (isFromHistory) {

@ -0,0 +1,174 @@
package com.todoroo.astrid.activity
import android.annotation.SuppressLint
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.todoroo.astrid.api.CaldavFilter
import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.GtasksFilter
import com.todoroo.astrid.api.TagFilter
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.billing.Inventory
import org.tasks.compose.drawer.DrawerItem
import org.tasks.data.CaldavDao
import org.tasks.data.TaskDao
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.PlaceFilter
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak")
class MainActivityViewModel @Inject constructor(
private val defaultFilterProvider: DefaultFilterProvider,
private val filterProvider: FilterProvider,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
private val inventory: Inventory,
private val colorProvider: ColorProvider,
private val caldavDao: CaldavDao,
private val preferences: Preferences,
) : ViewModel() {
data class State(
val begForMoney: Boolean = false,
val filter: Filter? = null,
val drawerOpen: Boolean = false,
val drawerItems: ImmutableList<DrawerItem> = persistentListOf(),
)
private val _state = MutableStateFlow(State())
val state = _state.asStateFlow()
private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
LocalBroadcastManager.REFRESH,
LocalBroadcastManager.REFRESH_LIST -> updateFilters()
}
}
}
fun setFilter(filter: Filter) {
_state.update { it.copy(filter = filter) }
defaultFilterProvider.lastViewedFilter = filter
}
fun setDrawerOpen(open: Boolean) {
_state.update { it.copy(drawerOpen = open) }
}
init {
localBroadcastManager.registerRefreshListReceiver(refreshReceiver)
updateFilters()
_state.update {
it.copy(
begForMoney = if (IS_GENERIC) !inventory.hasTasksAccount else !inventory.hasPro
)
}
}
override fun onCleared() {
localBroadcastManager.unregisterReceiver(refreshReceiver)
}
fun updateFilters() = viewModelScope.launch(Dispatchers.Default) {
filterProvider
.drawerItems()
.map { item ->
when (item) {
is Filter ->
DrawerItem.Filter(
title = item.title ?: "",
icon = getIcon(item),
color = getColor(item),
count = item.count.takeIf { it != NO_COUNT } ?: try {
taskDao.count(item)
} catch (e: Exception) {
Timber.e(e)
0
},
shareCount = if (item is CaldavFilter) item.principals else 0,
type = { item },
)
is NavigationDrawerSubheader ->
DrawerItem.Header(
title = item.title ?: "",
collapsed = item.isCollapsed,
hasError = item.error,
canAdd = item.addIntent != null,
type = { item },
)
else -> throw IllegalArgumentException()
}
}
.let { filters -> _state.update { it.copy(drawerItems = filters.toPersistentList()) } }
}
private fun getColor(filter: Filter): Int {
if (filter.tint != 0) {
val color = colorProvider.getThemeColor(filter.tint, true)
if (color.isFree || inventory.purchasedThemes()) {
return color.primaryColor
}
}
return 0
}
private fun getIcon(filter: Filter): Int {
if (filter.icon < 1000 || filter.icon == CustomIcons.PLACE || inventory.hasPro) {
val icon = CustomIcons.getIconResId(filter.icon)
if (icon != null) {
return icon
}
}
return when (filter) {
is TagFilter -> R.drawable.ic_outline_label_24px
is GtasksFilter,
is CaldavFilter -> R.drawable.ic_list_24px
is CustomFilter -> R.drawable.ic_outline_filter_list_24px
is PlaceFilter -> R.drawable.ic_outline_place_24px
else -> filter.icon
}
}
fun toggleCollapsed(subheader: NavigationDrawerSubheader) = viewModelScope.launch {
val collapsed = !subheader.isCollapsed
when (subheader.subheaderType) {
NavigationDrawerSubheader.SubheaderType.PREFERENCE -> {
preferences.setBoolean(subheader.id.toInt(), collapsed)
localBroadcastManager.broadcastRefreshList()
}
NavigationDrawerSubheader.SubheaderType.GOOGLE_TASKS,
NavigationDrawerSubheader.SubheaderType.CALDAV,
NavigationDrawerSubheader.SubheaderType.TASKS,
NavigationDrawerSubheader.SubheaderType.ETESYNC -> {
caldavDao.setCollapsed(subheader.id, collapsed)
localBroadcastManager.broadcastRefreshList()
}
else -> {}
}
}
}

@ -1,37 +0,0 @@
package com.todoroo.astrid.adapter
import android.content.Context
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import org.tasks.databinding.FilterAdapterActionBinding
import org.tasks.filters.NavigationDrawerAction
import org.tasks.themes.DrawableUtil
class ActionViewHolder internal constructor(
private val context: Context,
itemView: View,
private val onClick: (NavigationDrawerAction) -> Unit
) : RecyclerView.ViewHolder(itemView) {
private val row: View
private val text: TextView
private val icon: ImageView
init {
FilterAdapterActionBinding.bind(itemView).let {
row = it.row
text = it.text
icon = it.icon
}
}
fun bind(filter: NavigationDrawerAction) {
text.text = filter.title
icon.setImageDrawable(DrawableUtil.getWrapped(context, filter.icon))
row.setOnClickListener {
onClick.invoke(filter)
}
}
}

@ -18,7 +18,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.Channel
import org.tasks.activities.DragAndDropDiffer import org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.filters.NavigationDrawerAction
import org.tasks.filters.NavigationDrawerSubheader import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.themes.ColorProvider import org.tasks.themes.ColorProvider
import java.util.LinkedList import java.util.LinkedList
@ -38,7 +37,6 @@ class NavigationDrawerAdapter @Inject constructor(
DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> { DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> {
private lateinit var onClick: (FilterListItem?) -> Unit private lateinit var onClick: (FilterListItem?) -> Unit
private var selected: Filter? = null
override val channel = Channel<List<FilterListItem>>(Channel.UNLIMITED) override val channel = Channel<List<FilterListItem>>(Channel.UNLIMITED)
override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList() override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override val scope: CoroutineScope = override val scope: CoroutineScope =
@ -58,11 +56,6 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemCount() = items.size override fun getItemCount() = items.size
fun setSelected(selected: Filter?) {
this.selected = selected
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val type = FilterListItem.Type.values()[viewType] val type = FilterListItem.Type.values()[viewType]
val view = LayoutInflater.from(parent.context).inflate(type.layout, parent, false) val view = LayoutInflater.from(parent.context).inflate(type.layout, parent, false)
@ -70,20 +63,17 @@ class NavigationDrawerAdapter @Inject constructor(
FilterListItem.Type.ITEM -> FilterViewHolder( FilterListItem.Type.ITEM -> FilterViewHolder(
view, true, locale, activity, inventory, colorProvider) { onClickFilter(it) } view, true, locale, activity, inventory, colorProvider) { onClickFilter(it) }
FilterListItem.Type.SUBHEADER -> SubheaderViewHolder(view, subheaderClickHandler) FilterListItem.Type.SUBHEADER -> SubheaderViewHolder(view, subheaderClickHandler)
FilterListItem.Type.ACTION -> ActionViewHolder(activity, view) { onClickFilter(it) }
else -> SeparatorViewHolder(view)
} }
} }
private fun onClickFilter(filter: FilterListItem?) = private fun onClickFilter(filter: FilterListItem?) =
onClick(if (filter == selected) null else filter) onClick(filter)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position) val item = getItem(position)
when (item.itemType) { when (item.itemType) {
FilterListItem.Type.ITEM -> FilterListItem.Type.ITEM ->
(holder as FilterViewHolder).bind(item as Filter, item == selected, max(item.count, 0)) (holder as FilterViewHolder).bind(item as Filter, false, max(item.count, 0))
FilterListItem.Type.ACTION -> (holder as ActionViewHolder).bind(item as NavigationDrawerAction)
FilterListItem.Type.SUBHEADER -> FilterListItem.Type.SUBHEADER ->
(holder as SubheaderViewHolder).bind((item as NavigationDrawerSubheader)) (holder as SubheaderViewHolder).bind((item as NavigationDrawerSubheader))
else -> {} else -> {}

@ -1,6 +0,0 @@
package com.todoroo.astrid.adapter
import android.view.View
import androidx.recyclerview.widget.RecyclerView
class SeparatorViewHolder internal constructor(itemView: View) : RecyclerView.ViewHolder(itemView)

@ -8,6 +8,7 @@ import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager import org.tasks.LocalBroadcastManager
import org.tasks.data.CaldavDao import org.tasks.data.CaldavDao
import org.tasks.dialogs.NewFilterDialog import org.tasks.dialogs.NewFilterDialog
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.CALDAV import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.CALDAV
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.ETESYNC import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.ETESYNC
@ -16,7 +17,6 @@ import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.PREFERENCE
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.TASKS import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.TASKS
import org.tasks.preferences.MainPreferences import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject import javax.inject.Inject
class SubheaderClickHandler @Inject constructor( class SubheaderClickHandler @Inject constructor(
@ -42,7 +42,7 @@ class SubheaderClickHandler @Inject constructor(
override fun onAdd(subheader: NavigationDrawerSubheader) { override fun onAdd(subheader: NavigationDrawerSubheader) {
when (subheader.addIntentRc) { when (subheader.addIntentRc) {
NavigationDrawerFragment.REQUEST_NEW_FILTER -> FilterProvider.REQUEST_NEW_FILTER ->
NewFilterDialog.newFilterDialog().show( NewFilterDialog.newFilterDialog().show(
(activity as AppCompatActivity).supportFragmentManager, (activity as AppCompatActivity).supportFragmentManager,
FRAG_TAG_NEW_FILTER FRAG_TAG_NEW_FILTER
@ -55,6 +55,6 @@ class SubheaderClickHandler @Inject constructor(
activity.startActivity(Intent(activity, MainPreferences::class.java)) activity.startActivity(Intent(activity, MainPreferences::class.java))
companion object { companion object {
private const val FRAG_TAG_NEW_FILTER = "frag_tag_new_filter" const val FRAG_TAG_NEW_FILTER = "frag_tag_new_filter"
} }
} }

@ -15,7 +15,7 @@ import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
data class CaldavFilter( data class CaldavFilter(
val calendar: CaldavCalendar, val calendar: CaldavCalendar,
val principals: Int = 0, val principals: Int = 0,
override var count: Int = NO_COUNT, override val count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val title: String? override val title: String?
get() = calendar.name get() = calendar.name

@ -1,13 +1,11 @@
package com.todoroo.astrid.api package com.todoroo.astrid.api
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.tasks.themes.CustomIcons import org.tasks.themes.CustomIcons
@Parcelize @Parcelize
data class CustomFilter( data class CustomFilter(
val filter: org.tasks.data.Filter, val filter: org.tasks.data.Filter,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val title: String? override val title: String?
get() = filter.title get() = filter.title

@ -1,7 +1,6 @@
package com.todoroo.astrid.api package com.todoroo.astrid.api
import android.os.Parcelable import android.os.Parcelable
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
interface Filter : FilterListItem, Parcelable { interface Filter : FilterListItem, Parcelable {
@ -14,7 +13,8 @@ interface Filter : FilterListItem, Parcelable {
val tint: Int val tint: Int
get() = 0 get() = 0
@Deprecated("Remove this") @Deprecated("Remove this")
var count: Int val count: Int
get() = NO_COUNT
val order: Int val order: Int
get() = NO_ORDER get() = NO_ORDER
override val itemType: FilterListItem.Type override val itemType: FilterListItem.Type
@ -49,7 +49,6 @@ data class FilterImpl(
override val valuesForNewTasks: String? = null, override val valuesForNewTasks: String? = null,
override val icon: Int = -1, override val icon: Int = -1,
override val tint: Int = 0, override val tint: Int = 0,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override fun areItemsTheSame(other: FilterListItem): Boolean { override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is Filter && sql == other.sql return other is Filter && sql == other.sql

@ -10,8 +10,6 @@ interface FilterListItem {
enum class Type(@param:LayoutRes val layout: Int) { enum class Type(@param:LayoutRes val layout: Int) {
ITEM(R.layout.filter_adapter_row), ITEM(R.layout.filter_adapter_row),
ACTION(R.layout.filter_adapter_action),
SUBHEADER(R.layout.filter_adapter_subheader), SUBHEADER(R.layout.filter_adapter_subheader),
SEPARATOR(R.layout.filter_adapter_separator)
} }
} }

@ -15,7 +15,7 @@ import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
@Parcelize @Parcelize
data class GtasksFilter( data class GtasksFilter(
val list: CaldavCalendar, val list: CaldavCalendar,
override var count: Int = NO_COUNT, override val count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val title: String? override val title: String?
get() = list.name get() = list.name

@ -4,7 +4,6 @@ import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.Join import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.Query import com.todoroo.andlib.sql.Query
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.tasks.data.CaldavCalendar import org.tasks.data.CaldavCalendar
@ -18,7 +17,6 @@ import org.tasks.data.UserActivity
data class SearchFilter( data class SearchFilter(
override val title: String, override val title: String,
val query: String, val query: String,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val sql: String override val sql: String
get() { get() {

@ -14,7 +14,7 @@ import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
@Parcelize @Parcelize
data class TagFilter( data class TagFilter(
val tagData: TagData, val tagData: TagData,
override var count: Int = NO_COUNT, override val count: Int = NO_COUNT,
override var filterOverride: String? = null, override var filterOverride: String? = null,
) : AstridOrderingFilter { ) : AstridOrderingFilter {
override val title: String? override val title: String?

@ -30,20 +30,14 @@ import org.tasks.filters.TodayFilter
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import javax.inject.Inject import javax.inject.Inject
/**
* Exposes Astrid's built in filters to the NavigationDrawerFragment
*
* @author Tim Su <tim></tim>@todoroo.com>
*/
class BuiltInFilterExposer @Inject constructor( class BuiltInFilterExposer @Inject constructor(
@param:ApplicationContext private val context: Context, @param:ApplicationContext private val context: Context,
private val preferences: Preferences, private val preferences: Preferences,
private val taskDao: TaskDao) { private val taskDao: TaskDao
) {
val myTasksFilter: Filter val myTasksFilter: Filter
get() { get() = getMyTasksFilter(context.resources)
return getMyTasksFilter(context.resources)
}
suspend fun filters(): List<Filter> { suspend fun filters(): List<Filter> {
val r = context.resources val r = context.resources
@ -67,9 +61,7 @@ class BuiltInFilterExposer @Inject constructor(
} }
companion object { companion object {
fun getMyTasksFilter(r: Resources): AstridOrderingFilter { fun getMyTasksFilter(r: Resources) = MyTasksFilter(r.getString(R.string.BFE_Active))
return MyTasksFilter(r.getString(R.string.BFE_Active))
}
fun getTodayFilter(r: Resources): Filter { fun getTodayFilter(r: Resources): Filter {
return TodayFilter(r.getString(R.string.today)) return TodayFilter(r.getString(R.string.today))
@ -148,7 +140,7 @@ class BuiltInFilterExposer @Inject constructor(
) )
fun getRecentlyModifiedFilter(r: Resources) = fun getRecentlyModifiedFilter(r: Resources) =
RecentlyModifiedFilter(r.getString(R.string.BFE_Recent)) RecentlyModifiedFilter(r.getString(R.string.BFE_Recent))
fun getSnoozedFilter(r: Resources) = SnoozedFilter(r.getString(R.string.filter_snoozed)) fun getSnoozedFilter(r: Resources) = SnoozedFilter(r.getString(R.string.filter_snoozed))

@ -100,11 +100,6 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
private fun updateFilters() = lifecycleScope.launch { private fun updateFilters() = lifecycleScope.launch {
filterProvider filterProvider
.drawerCustomizationItems() .drawerCustomizationItems()
.onEach { f ->
if (f is Filter) {
f.count = 0
}
}
.let { adapter.submitList(it) } .let { adapter.submitList(it) }
} }

@ -0,0 +1,5 @@
package org.tasks.compose.drawer
enum class DrawerAction {
PURCHASE, CUSTOMIZE_DRAWER, SETTINGS, HELP_AND_FEEDBACK
}

@ -0,0 +1,22 @@
package org.tasks.compose.drawer
import org.tasks.filters.NavigationDrawerSubheader
sealed interface DrawerItem {
data class Filter(
val title: String,
val icon: Int,
val color: Int = 0,
val count: Int = 0,
val shareCount: Int = 0,
val selected: Boolean = false,
val type: () -> com.todoroo.astrid.api.Filter,
) : DrawerItem
data class Header(
val title: String,
val collapsed: Boolean,
val hasError: Boolean,
val canAdd: Boolean,
val type: () -> NavigationDrawerSubheader,
) : DrawerItem
}

@ -0,0 +1,318 @@
package org.tasks.compose.drawer
import android.content.res.Configuration
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.mandatorySystemGestures
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.ContentAlpha
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.ExpandMore
import androidx.compose.material.icons.outlined.PeopleOutline
import androidx.compose.material.icons.outlined.PermIdentity
import androidx.compose.material.icons.outlined.SyncProblem
import androidx.compose.material3.Divider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.astrid.api.FilterImpl
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.extensions.formatNumber
import org.tasks.filters.NavigationDrawerSubheader
import timber.log.Timber
@Composable
fun TaskListDrawer(
begForMoney: Boolean,
filters: ImmutableList<DrawerItem>,
onClick: (DrawerItem) -> Unit,
onDrawerAction: (DrawerAction) -> Unit,
onAddClick: (DrawerItem.Header) -> Unit,
onErrorClick: () -> Unit,
) {
val insets = WindowInsets.mandatorySystemGestures
LazyColumn(
modifier = Modifier
.padding(
bottom = insets
.asPaddingValues()
.calculateBottomPadding()
)
.animateContentSize(
animationSpec = spring(
dampingRatio = Spring.DampingRatioNoBouncy,
stiffness = Spring.StiffnessMedium
)
)
) {
items(items = filters) {
when (it) {
is DrawerItem.Filter -> FilterItem(item = it, onClick = { onClick(it) })
is DrawerItem.Header -> HeaderItem(
item = it,
canAdd = it.canAdd,
toggleCollapsed = { onClick(it) },
onAddClick = { onAddClick(it) },
onErrorClick = onErrorClick,
)
}
}
item {
Divider(modifier = Modifier.fillMaxWidth())
}
if (begForMoney) {
item {
MenuAction(
icon = R.drawable.ic_outline_attach_money_24px,
title = if (IS_GENERIC) R.string.TLA_menu_donate else R.string.name_your_price
) {
onDrawerAction(DrawerAction.PURCHASE)
}
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_edit_24px,
title = R.string.manage_drawer
) {
onDrawerAction(DrawerAction.CUSTOMIZE_DRAWER)
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_settings_24px,
title = R.string.TLA_menu_settings
) {
onDrawerAction(DrawerAction.SETTINGS)
}
}
item {
MenuAction(
icon = R.drawable.ic_outline_help_outline_24px,
title = R.string.help_and_feedback
) {
onDrawerAction(DrawerAction.HELP_AND_FEEDBACK)
}
}
}
}
@Composable
private fun FilterItem(
item: DrawerItem.Filter,
onClick: () -> Unit,
) {
MenuRow(
onClick = onClick,
) {
if (item.icon != -1) {
DrawerIcon(icon = item.icon, color = item.color)
}
Spacer(modifier = Modifier.width(16.dp))
Text(
text = item.title,
color = MaterialTheme.colors.onSurface,
modifier = Modifier.weight(1f),
)
if (item.shareCount > 0) {
Icon(
imageVector = when (item.shareCount) {
1 -> Icons.Outlined.PermIdentity
else -> Icons.Outlined.PeopleOutline
},
contentDescription = null,
tint = MaterialTheme.colors.onSurface.copy(
alpha = ContentAlpha.medium
),
)
}
Box(
modifier = Modifier.width(48.dp),
contentAlignment = Alignment.CenterEnd,
) {
if (item.count > 0) {
val locale = LocalConfiguration.current.locales[0]
Text(
text = locale.formatNumber(item.count),
color = MaterialTheme.colors.onSurface,
)
}
}
}
}
@Composable
private fun MenuAction(
icon: Int,
title: Int,
onClick: () -> Unit,
) {
MenuRow(onClick = onClick) {
DrawerIcon(icon = icon)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = stringResource(id = title),
color = MaterialTheme.colors.onSurface,
modifier = Modifier.weight(1f),
)
}
}
@Composable
private fun DrawerIcon(icon: Int, color: Int = 0) {
Icon(
modifier = Modifier.size(24.dp),
painter = painterResource(id = icon),
contentDescription = null,
tint = when (color) {
0 -> MaterialTheme.colors.onSurface
else -> Color(color)
}.copy(alpha = ContentAlpha.medium)
)
}
@Composable
private fun HeaderItem(
item: DrawerItem.Header,
canAdd: Boolean,
toggleCollapsed: () -> Unit,
onAddClick: () -> Unit,
onErrorClick: () -> Unit,
) {
Column {
Divider(modifier = Modifier.fillMaxWidth())
MenuRow(
padding = PaddingValues(start = 16.dp),
onClick = toggleCollapsed,
) {
Text(
modifier = Modifier.weight(1f),
text = item.title,
color = MaterialTheme.colors.onSurface,
)
IconButton(onClick = toggleCollapsed) {
val rotation by animateFloatAsState(
targetValue = if (item.collapsed) 0f else 180f,
animationSpec = tween(250),
label = "arrow rotation",
)
Timber.d("rotation: $rotation")
Icon(
modifier = Modifier.rotate(rotation),
imageVector = Icons.Outlined.ExpandMore,
contentDescription = null,
tint = MaterialTheme.colors.onSurface,
)
}
if (canAdd) {
IconButton(onClick = onAddClick) {
Icon(
imageVector = Icons.Outlined.Add,
contentDescription = null,
tint = MaterialTheme.colors.onSurface,
)
}
}
if (item.hasError) {
IconButton(onClick = onErrorClick) {
Icon(
imageVector = Icons.Outlined.SyncProblem,
contentDescription = null,
tint = MaterialTheme.colors.error,
)
}
}
}
}
}
@Composable
private fun MenuRow(
padding: PaddingValues = PaddingValues(horizontal = 16.dp),
onClick: () -> Unit,
content: @Composable RowScope.() -> Unit,
) {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.height(48.dp)
.padding(padding)
.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
content = content
)
}
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
fun MenuPreview() {
MdcTheme {
TaskListDrawer(
filters = persistentListOf(
DrawerItem.Filter(
title = "My Tasks",
icon = R.drawable.ic_outline_all_inbox_24px,
type = { FilterImpl() },
),
DrawerItem.Header(
title = "Filters",
collapsed = false,
canAdd = true,
hasError = false,
type = {
NavigationDrawerSubheader(
null,
false,
false,
NavigationDrawerSubheader.SubheaderType.PREFERENCE,
0L,
0,
null
)
},
)
),
onClick = {},
onDrawerAction = {},
begForMoney = true,
onAddClick = {},
onErrorClick = {},
)
}
}

@ -4,6 +4,7 @@ import android.app.Dialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.api.CustomFilterCriterion import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.core.CriterionInstance import com.todoroo.astrid.core.CriterionInstance
import com.todoroo.astrid.core.CriterionInstance.Companion.TYPE_INTERSECT import com.todoroo.astrid.core.CriterionInstance.Companion.TYPE_INTERSECT
@ -14,7 +15,6 @@ import dagger.hilt.android.AndroidEntryPoint
import org.tasks.R import org.tasks.R
import org.tasks.activities.FilterSettingsActivity import org.tasks.activities.FilterSettingsActivity
import org.tasks.filters.FilterCriteriaProvider import org.tasks.filters.FilterCriteriaProvider
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
@ -123,7 +123,7 @@ class NewFilterDialog : DialogFragment() {
intent.putExtra(FilterSettingsActivity.EXTRA_TITLE, title) intent.putExtra(FilterSettingsActivity.EXTRA_TITLE, title)
intent.putExtra(FilterSettingsActivity.EXTRA_CRITERIA, serialize(list)) intent.putExtra(FilterSettingsActivity.EXTRA_CRITERIA, serialize(list))
} }
activity?.startActivityForResult(intent, NavigationDrawerFragment.REQUEST_NEW_LIST) activity?.startActivityForResult(intent, MainActivity.REQUEST_NEW_LIST)
dismiss() dismiss()
} }

@ -1,11 +0,0 @@
package org.tasks.extensions
import android.content.res.Resources
import android.util.TypedValue
val Number.dp: Float
get() = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
this.toFloat(),
Resources.getSystem().displayMetrics
)

@ -9,12 +9,9 @@ data class CaldavFilters(
@JvmField val count: Int, @JvmField val count: Int,
@JvmField val principals: Int, @JvmField val principals: Int,
) { ) {
fun toCaldavFilter(): CaldavFilter { fun toCaldavFilter(): CaldavFilter = CaldavFilter(
val filter = CaldavFilter( calendar = caldavCalendar,
calendar = caldavCalendar, principals = principals,
principals = principals, count = count,
) )
filter.count = count
return filter
}
} }

@ -2,6 +2,7 @@ package org.tasks.filters
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.api.CustomFilter import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_ORDER import com.todoroo.astrid.api.Filter.Companion.NO_ORDER
@ -10,11 +11,8 @@ import com.todoroo.astrid.core.BuiltInFilterExposer
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import org.tasks.BuildConfig import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.activities.GoogleTaskListSettingsActivity import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.activities.TagSettingsActivity import org.tasks.activities.TagSettingsActivity
import org.tasks.billing.Inventory
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.CaldavAccount import org.tasks.data.CaldavAccount
import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC import org.tasks.data.CaldavAccount.Companion.TYPE_ETESYNC
@ -27,28 +25,24 @@ import org.tasks.data.LocationDao
import org.tasks.data.TagDataDao import org.tasks.data.TagDataDao
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType import org.tasks.filters.NavigationDrawerSubheader.SubheaderType
import org.tasks.location.LocationPickerActivity import org.tasks.location.LocationPickerActivity
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject import javax.inject.Inject
class FilterProvider @Inject constructor( class FilterProvider @Inject constructor(
@param:ApplicationContext private val context: Context, @param:ApplicationContext private val context: Context,
private val inventory: Inventory,
private val builtInFilterExposer: BuiltInFilterExposer, private val builtInFilterExposer: BuiltInFilterExposer,
private val filterDao: FilterDao, private val filterDao: FilterDao,
private val tagDataDao: TagDataDao, private val tagDataDao: TagDataDao,
private val googleTaskListDao: GoogleTaskListDao, private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDao, private val caldavDao: CaldavDao,
private val preferences: Preferences, private val preferences: Preferences,
private val locationDao: LocationDao) { private val locationDao: LocationDao
) {
suspend fun listPickerItems(): List<FilterListItem> = suspend fun listPickerItems(): List<FilterListItem> =
caldavFilters(false) caldavFilters(false)
suspend fun navDrawerItems(): List<FilterListItem> = suspend fun drawerItems(): List<FilterListItem> =
getAllFilters(hideUnused = true).plus(navDrawerFooter) getAllFilters(showCreate = true)
suspend fun filterPickerItems(): List<FilterListItem> = suspend fun filterPickerItems(): List<FilterListItem> =
getAllFilters(showCreate = false) getAllFilters(showCreate = false)
@ -95,7 +89,7 @@ class FilterProvider @Inject constructor(
collapsed, collapsed,
SubheaderType.PREFERENCE, SubheaderType.PREFERENCE,
R.string.p_collapse_filters.toLong(), R.string.p_collapse_filters.toLong(),
NavigationDrawerFragment.REQUEST_NEW_FILTER, REQUEST_NEW_FILTER,
if (showCreate) Intent() else null)) if (showCreate) Intent() else null))
.apply { if (collapsed) return this } .apply { if (collapsed) return this }
.plusAllIf(showBuiltIn) { .plusAllIf(showBuiltIn) {
@ -116,7 +110,7 @@ class FilterProvider @Inject constructor(
collapsed, collapsed,
SubheaderType.PREFERENCE, SubheaderType.PREFERENCE,
R.string.p_collapse_tags.toLong(), R.string.p_collapse_tags.toLong(),
NavigationDrawerFragment.REQUEST_NEW_LIST, MainActivity.REQUEST_NEW_LIST,
if (showCreate) { if (showCreate) {
Intent(context, TagSettingsActivity::class.java) Intent(context, TagSettingsActivity::class.java)
} else { } else {
@ -143,7 +137,7 @@ class FilterProvider @Inject constructor(
collapsed, collapsed,
SubheaderType.PREFERENCE, SubheaderType.PREFERENCE,
R.string.p_collapse_locations.toLong(), R.string.p_collapse_locations.toLong(),
NavigationDrawerFragment.REQUEST_NEW_PLACE, MainActivity.REQUEST_NEW_PLACE,
if (showCreate) { if (showCreate) {
Intent(context, LocationPickerActivity::class.java) Intent(context, LocationPickerActivity::class.java)
} else { } else {
@ -176,45 +170,6 @@ class FilterProvider @Inject constructor(
.toList() .toList()
.plusAllIf(BuildConfig.DEBUG) { getDebugFilters() } .plusAllIf(BuildConfig.DEBUG) { getDebugFilters() }
private val navDrawerFooter: List<FilterListItem>
get() = listOf(NavigationDrawerSeparator())
.plusIf(IS_GENERIC && !inventory.hasTasksAccount) {
NavigationDrawerAction(
context.getString(R.string.TLA_menu_donate),
R.drawable.ic_outline_attach_money_24px,
NavigationDrawerFragment.REQUEST_DONATE)
}
.plusIf(!inventory.hasPro) {
NavigationDrawerAction(
context.getString(R.string.name_your_price),
R.drawable.ic_outline_attach_money_24px,
NavigationDrawerFragment.REQUEST_PURCHASE)
}
.plus(
NavigationDrawerAction(
context.getString(R.string.manage_drawer),
R.drawable.ic_outline_edit_24px,
0,
Intent(context, NavigationDrawerCustomization::class.java)
)
)
.plus(
NavigationDrawerAction(
context.getString(R.string.TLA_menu_settings),
R.drawable.ic_outline_settings_24px,
NavigationDrawerFragment.REQUEST_SETTINGS,
Intent(context, MainPreferences::class.java)
)
)
.plus(
NavigationDrawerAction(
context.getString(R.string.help_and_feedback),
R.drawable.ic_outline_help_outline_24px,
0,
Intent(context, HelpAndFeedback::class.java)
)
)
private suspend fun googleTaskFilter(account: CaldavAccount, showCreate: Boolean): List<FilterListItem> = private suspend fun googleTaskFilter(account: CaldavAccount, showCreate: Boolean): List<FilterListItem> =
listOf( listOf(
NavigationDrawerSubheader( NavigationDrawerSubheader(
@ -223,7 +178,7 @@ class FilterProvider @Inject constructor(
account.isCollapsed, account.isCollapsed,
SubheaderType.GOOGLE_TASKS, SubheaderType.GOOGLE_TASKS,
account.id, account.id,
NavigationDrawerFragment.REQUEST_NEW_LIST, MainActivity.REQUEST_NEW_LIST,
if (showCreate) { if (showCreate) {
Intent(context, GoogleTaskListSettingsActivity::class.java) Intent(context, GoogleTaskListSettingsActivity::class.java)
.putExtra(GoogleTaskListSettingsActivity.EXTRA_ACCOUNT, account) .putExtra(GoogleTaskListSettingsActivity.EXTRA_ACCOUNT, account)
@ -267,7 +222,7 @@ class FilterProvider @Inject constructor(
else -> SubheaderType.CALDAV else -> SubheaderType.CALDAV
}, },
account.id, account.id,
NavigationDrawerFragment.REQUEST_NEW_LIST, MainActivity.REQUEST_NEW_LIST,
if (showCreate) { if (showCreate) {
Intent(context, account.listSettingsClass()) Intent(context, account.listSettingsClass())
.putExtra( .putExtra(
@ -285,6 +240,7 @@ class FilterProvider @Inject constructor(
.sort()) .sort())
companion object { companion object {
const val REQUEST_NEW_FILTER = 101015
private val COMPARATOR = Comparator<Filter> { f1, f2 -> private val COMPARATOR = Comparator<Filter> { f1, f2 ->
when { when {
f1.order == NO_ORDER && f2.order == NO_ORDER -> f1.order == NO_ORDER && f2.order == NO_ORDER ->
@ -307,9 +263,6 @@ class FilterProvider @Inject constructor(
private suspend fun <T> Collection<T>.plusAllIf(predicate: Boolean, item: suspend () -> Iterable<T>): List<T> = private suspend fun <T> Collection<T>.plusAllIf(predicate: Boolean, item: suspend () -> Iterable<T>): List<T> =
plus(if (predicate) item() else emptyList()) plus(if (predicate) item() else emptyList())
private fun <T> Iterable<T>.plusIf(predicate: Boolean, item: () -> T): Iterable<T> =
if (predicate) plus(item()) else this
private fun <T> Iterable<T>.filterIf(predicate: Boolean, predicate2: (T) -> Boolean): Iterable<T> = private fun <T> Iterable<T>.filterIf(predicate: Boolean, predicate2: (T) -> Boolean): Iterable<T> =
if (predicate) filter(predicate2) else this if (predicate) filter(predicate2) else this
} }

@ -8,9 +8,8 @@ data class GoogleTaskFilters(
@JvmField @Embedded val googleTaskList: CaldavCalendar, @JvmField @Embedded val googleTaskList: CaldavCalendar,
@JvmField val count: Int, @JvmField val count: Int,
) { ) {
fun toGtasksFilter(): GtasksFilter { fun toGtasksFilter(): GtasksFilter = GtasksFilter(
val filter = GtasksFilter(googleTaskList) list = googleTaskList,
filter.count = count count = count,
return filter )
}
} }

@ -7,9 +7,8 @@ data class LocationFilters(
@JvmField @Embedded var place: Place, @JvmField @Embedded var place: Place,
@JvmField var count: Int @JvmField var count: Int
) { ) {
fun toLocationFilter(): PlaceFilter { fun toLocationFilter(): PlaceFilter = PlaceFilter(
val filter = PlaceFilter(place) place = place,
filter.count = count count = count,
return filter )
}
} }

@ -3,7 +3,6 @@ package org.tasks.filters
import com.todoroo.andlib.sql.Criterion import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -14,7 +13,6 @@ import org.tasks.themes.CustomIcons
data class MyTasksFilter( data class MyTasksFilter(
override val title: String, override val title: String,
override var filterOverride: String? = null, override var filterOverride: String? = null,
override var count: Int = NO_COUNT,
) : AstridOrderingFilter { ) : AstridOrderingFilter {
override val icon: Int override val icon: Int
get() = CustomIcons.ALL_INBOX get() = CustomIcons.ALL_INBOX

@ -1,15 +0,0 @@
package org.tasks.filters
import android.content.Intent
import com.todoroo.astrid.api.FilterListItem
data class NavigationDrawerAction(
val title: String,
val icon: Int,
val requestCode: Int,
val intent: Intent? = null,
) : FilterListItem {
override val itemType = FilterListItem.Type.ACTION
override fun areItemsTheSame(other: FilterListItem) = this == other
}

@ -1,11 +0,0 @@
package org.tasks.filters
import com.todoroo.astrid.api.FilterListItem
class NavigationDrawerSeparator : FilterListItem {
override val itemType = FilterListItem.Type.SEPARATOR
override fun areItemsTheSame(other: FilterListItem): Boolean {
return other is NavigationDrawerSeparator
}
}

@ -3,7 +3,6 @@ package org.tasks.filters
import com.todoroo.andlib.sql.Join import com.todoroo.andlib.sql.Join
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -13,7 +12,6 @@ import org.tasks.notifications.Notification
@Parcelize @Parcelize
data class NotificationsFilter( data class NotificationsFilter(
override val title: String, override val title: String,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val icon: Int override val icon: Int
get() = R.drawable.ic_outline_notifications_24px get() = R.drawable.ic_outline_notifications_24px

@ -19,7 +19,7 @@ import org.tasks.themes.CustomIcons
@Parcelize @Parcelize
data class PlaceFilter( data class PlaceFilter(
val place: Place, val place: Place,
override var count: Int = NO_COUNT, override val count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val valuesForNewTasks: String override val valuesForNewTasks: String
get() = AndroidUtilities.mapToSerializedString(mapOf(Place.KEY to place.uid!!)) get() = AndroidUtilities.mapToSerializedString(mapOf(Place.KEY to place.uid!!))

@ -4,7 +4,6 @@ import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Order.Companion.desc import com.todoroo.andlib.sql.Order.Companion.desc
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -14,7 +13,6 @@ import org.tasks.time.DateTime
@Parcelize @Parcelize
data class RecentlyModifiedFilter( data class RecentlyModifiedFilter(
override val title: String, override val title: String,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val icon: Int override val icon: Int
get() = CustomIcons.HISTORY get() = CustomIcons.HISTORY

@ -4,7 +4,6 @@ import com.todoroo.andlib.sql.Criterion.Companion.and
import com.todoroo.andlib.sql.Join.Companion.inner import com.todoroo.andlib.sql.Join.Companion.inner
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.astrid.api.Filter import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -15,7 +14,6 @@ import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
@Parcelize @Parcelize
data class SnoozedFilter( data class SnoozedFilter(
override val title: String, override val title: String,
override var count: Int = NO_COUNT,
) : Filter { ) : Filter {
override val icon: Int override val icon: Int
get() = R.drawable.ic_snooze_white_24dp get() = R.drawable.ic_snooze_white_24dp

@ -8,9 +8,8 @@ data class TagFilters(
@JvmField @Embedded var tagData: TagData, @JvmField @Embedded var tagData: TagData,
@JvmField var count: Int, @JvmField var count: Int,
) { ) {
fun toTagFilter(): TagFilter { fun toTagFilter(): TagFilter = TagFilter(
val filter = TagFilter(tagData) tagData = tagData,
filter.count = count count = count,
return filter )
}
} }

@ -4,7 +4,6 @@ import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.QueryTemplate import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.andlib.utility.AndroidUtilities import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.api.AstridOrderingFilter import com.todoroo.astrid.api.AstridOrderingFilter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem import com.todoroo.astrid.api.FilterListItem
import com.todoroo.astrid.api.PermaSql import com.todoroo.astrid.api.PermaSql
import com.todoroo.astrid.data.Task import com.todoroo.astrid.data.Task
@ -16,7 +15,6 @@ import org.tasks.themes.CustomIcons
data class TodayFilter( data class TodayFilter(
override val title: String, override val title: String,
override var filterOverride: String? = null, override var filterOverride: String? = null,
override var count: Int = NO_COUNT,
) : AstridOrderingFilter { ) : AstridOrderingFilter {
override val sql: String override val sql: String
get() = QueryTemplate() get() = QueryTemplate()

@ -36,7 +36,6 @@ import org.tasks.themes.ThemeBase.DEFAULT_BASE_THEME
import org.tasks.themes.ThemeBase.EXTRA_THEME_OVERRIDE import org.tasks.themes.ThemeBase.EXTRA_THEME_OVERRIDE
import org.tasks.themes.ThemeColor import org.tasks.themes.ThemeColor
import org.tasks.themes.ThemeColor.getLauncherColor import org.tasks.themes.ThemeColor.getLauncherColor
import org.tasks.ui.NavigationDrawerFragment.Companion.REQUEST_PURCHASE
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ -246,6 +245,7 @@ class LookAndFeel : InjectingPreferenceFragment() {
private const val REQUEST_ACCENT_PICKER = 10003 private const val REQUEST_ACCENT_PICKER = 10003
private const val REQUEST_LAUNCHER_PICKER = 10004 private const val REQUEST_LAUNCHER_PICKER = 10004
private const val REQUEST_LOCALE = 10006 private const val REQUEST_LOCALE = 10006
private const val REQUEST_PURCHASE = 10007
private const val FRAG_TAG_LOCALE_PICKER = "frag_tag_locale_picker" private const val FRAG_TAG_LOCALE_PICKER = "frag_tag_locale_picker"
private const val FRAG_TAG_THEME_PICKER = "frag_tag_theme_picker" private const val FRAG_TAG_THEME_PICKER = "frag_tag_theme_picker"
private const val FRAG_TAG_COLOR_PICKER = "frag_tag_color_picker" private const val FRAG_TAG_COLOR_PICKER = "frag_tag_color_picker"

@ -1,122 +0,0 @@
package org.tasks.ui
import android.app.Dialog
import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.todoroo.astrid.adapter.NavigationDrawerAdapter
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.FilterListItem
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.tasks.R
import org.tasks.billing.PurchaseActivity
import org.tasks.extensions.Context.openUri
import org.tasks.filters.NavigationDrawerAction
import org.tasks.intents.TaskIntents
import org.tasks.preferences.Preferences
import javax.inject.Inject
@AndroidEntryPoint
class NavigationDrawerFragment : BottomSheetDialogFragment() {
@Inject lateinit var adapter: NavigationDrawerAdapter
@Inject lateinit var preferences: Preferences
override fun getTheme() = R.style.CustomBottomSheetDialog
private lateinit var recyclerView: RecyclerView
private val viewModel: NavigationDrawerViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.getParcelable<Filter>(EXTRA_SELECTED)?.let {
viewModel.setSelected(it)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setOnShowListener {
val bottomSheet =
dialog.findViewById<FrameLayout>(com.google.android.material.R.id.design_bottom_sheet)
val behavior = BottomSheetBehavior.from(bottomSheet!!)
behavior.skipCollapsed = true
if (preferences.isTopAppBar) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
} else if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
behavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
}
return dialog
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View? {
val layout = inflater.inflate(R.layout.fragment_navigation_drawer, container, false)
recyclerView = layout.findViewById(R.id.recycler_view)
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
adapter.setOnClick(this::onFilterItemSelected)
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = adapter
viewModel
.viewState
.flowWithLifecycle(lifecycle, Lifecycle.State.RESUMED)
.onEach {
adapter.setSelected(it.selected)
adapter.submitList(it.filters)
}
.launchIn(lifecycleScope)
return layout
}
private fun onFilterItemSelected(item: FilterListItem?) {
if (item is Filter) {
viewModel.setSelected(item)
activity?.startActivity(TaskIntents.getTaskListIntent(activity, item))
} else if (item is NavigationDrawerAction) {
when (item.requestCode) {
REQUEST_PURCHASE ->
startActivity(Intent(context, PurchaseActivity::class.java))
REQUEST_DONATE -> context?.openUri(R.string.url_donate)
else -> item.intent?.let {
activity?.startActivityForResult(it, item.requestCode)
}
}
}
dismiss()
}
companion object {
const val REQUEST_NEW_LIST = 10100
const val REQUEST_SETTINGS = 10101
const val REQUEST_PURCHASE = 10102
const val REQUEST_DONATE = 10103
const val REQUEST_NEW_PLACE = 10104
const val REQUEST_NEW_FILTER = 101015
private const val EXTRA_SELECTED = "extra_selected"
fun newNavigationDrawer(selected: Filter?): NavigationDrawerFragment {
val fragment = NavigationDrawerFragment()
fragment.arguments = Bundle().apply {
putParcelable(EXTRA_SELECTED, selected)
}
return fragment
}
}
}

@ -1,75 +0,0 @@
package org.tasks.ui
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.api.Filter.Companion.NO_COUNT
import com.todoroo.astrid.api.FilterListItem
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.tasks.LocalBroadcastManager
import org.tasks.data.TaskDao
import org.tasks.filters.FilterProvider
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class NavigationDrawerViewModel @Inject constructor(
private val filterProvider: FilterProvider,
private val taskDao: TaskDao,
private val localBroadcastManager: LocalBroadcastManager,
) : ViewModel() {
data class ViewState(
val selected: Filter? = null,
val filters: List<FilterListItem> = emptyList(),
)
private val _viewState = MutableStateFlow(ViewState())
private val refreshReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
LocalBroadcastManager.REFRESH,
LocalBroadcastManager.REFRESH_LIST -> updateFilters()
}
}
}
val viewState: StateFlow<ViewState>
get() = _viewState.asStateFlow()
fun setSelected(filter: Filter?) {
_viewState.update { it.copy(selected = filter) }
}
fun updateFilters() = viewModelScope.launch {
filterProvider
.navDrawerItems()
.onEach {
if (it is Filter && it.count == NO_COUNT) {
it.count = try {
taskDao.count(it)
} catch (e: Exception) {
Timber.e(e)
0
}
}
}
.let { filters -> _viewState.update { it.copy(filters = filters) } }
}
override fun onCleared() {
localBroadcastManager.unregisterReceiver(refreshReceiver)
}
init {
localBroadcastManager.registerRefreshListReceiver(refreshReceiver)
updateFilters()
}
}

@ -29,5 +29,10 @@
</LinearLayout> </LinearLayout>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

@ -1,47 +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"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/row"
android:background="@drawable/drawer_background_selector"
android:foreground="?attr/selectableItemBackground"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="12dp"
android:paddingBottom="12dp"
android:paddingStart="@dimen/keyline_first"
android:paddingEnd="0dp">
<ImageView
android:id="@+id/icon"
android:src="@drawable/ic_outline_add_24px"
app:tint="@color/text_primary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:background="@null"
android:layout_gravity="center_vertical"
android:alpha="@dimen/alpha_secondary"
android:scaleType="center"/>
<TextView
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@id/icon"
android:background="@null"
android:paddingStart="@dimen/keyline_second"
android:paddingEnd="@dimen/keyline_first"
android:duplicateParentState="true"
android:ellipsize="end"
android:fontFamily="@string/font_fontFamily_medium"
android:gravity="start|center_vertical"
android:lines="1"
android:singleLine="true"
android:textAlignment="viewStart"
android:textSize="14sp"
android:textColor="@color/text_primary"
tools:ignore="UnusedAttribute"/>
</RelativeLayout>

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/drawer_background_selector"
android:focusable="false"
android:orientation="vertical">
<View
android:id="@+id/divider"
android:paddingBottom="4dp"
style="@style/horizontal_divider"
android:layout_gravity="top" />
</LinearLayout>

@ -1,8 +0,0 @@
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler_view"
android:paddingTop="@dimen/bottom_sheet_corners"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"/>

@ -18,5 +18,10 @@
android:visibility="gone" android:visibility="gone"
tools:ignore="InconsistentLayout" /> tools:ignore="InconsistentLayout" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout> </LinearLayout>

@ -264,19 +264,4 @@
<item name="hintTextColor">?attr/colorSecondary</item> <item name="hintTextColor">?attr/colorSecondary</item>
</style> </style>
<style name="CustomBottomSheetDialog" parent="@style/ThemeOverlay.MaterialComponents.BottomSheetDialog">
<item name="bottomSheetStyle">@style/CustomBottomSheet</item>
</style>
<style name="CustomBottomSheet" parent="Widget.MaterialComponents.BottomSheet">
<item name="shapeAppearanceOverlay">@style/CustomShapeAppearanceBottomSheetDialog</item>
</style>
<style name="CustomShapeAppearanceBottomSheetDialog" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSizeTopRight">@dimen/bottom_sheet_corners</item>
<item name="cornerSizeTopLeft">@dimen/bottom_sheet_corners</item>
<item name="cornerSizeBottomRight">0dp</item>
<item name="cornerSizeBottomLeft">0dp</item>
</style>
</resources> </resources>

@ -148,11 +148,33 @@ unstable class MainActivity {
runtime var alarmDao: AlarmDao runtime var alarmDao: AlarmDao
unstable var eventBus: MutableSharedFlow<MainActivityEvent>{ org.tasks.ui.MainActivityEventBus } unstable var eventBus: MutableSharedFlow<MainActivityEvent>{ org.tasks.ui.MainActivityEventBus }
unstable var firebase: Firebase unstable var firebase: Firebase
unstable val viewModel$delegate: Lazy<MainActivityViewModel>
stable var currentNightMode: Int stable var currentNightMode: Int
stable var currentPro: Boolean stable var currentPro: Boolean
runtime var filter: Filter?
unstable var actionMode: ActionMode? unstable var actionMode: ActionMode?
unstable var binding: TaskListActivityBinding unstable var binding: TaskListActivityBinding
unstable val settingsRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?>
<runtime stability> = Unstable
}
stable class State {
stable val begForMoney: Boolean
runtime val filter: Filter?
stable val drawerOpen: Boolean
runtime val drawerItems: ImmutableList<DrawerItem>
<runtime stability> =
}
unstable class MainActivityViewModel {
unstable val defaultFilterProvider: DefaultFilterProvider
unstable val filterProvider: FilterProvider
stable val taskDao: TaskDao
unstable val localBroadcastManager: LocalBroadcastManager
unstable val inventory: Inventory
unstable val colorProvider: ColorProvider
stable val caldavDao: CaldavDao
unstable val preferences: Preferences
unstable val _state: MutableStateFlow<State>
unstable val state: StateFlow<State>
stable val refreshReceiver: <no name provided>
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class ShareLinkActivity { unstable class ShareLinkActivity {
@ -222,14 +244,6 @@ unstable class TaskListFragment {
unstable val listSettingsRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?> unstable val listSettingsRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?>
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class ActionViewHolder {
unstable val context: Context
stable val onClick: Function1<NavigationDrawerAction, Unit>
unstable val row: View
unstable val text: TextView
unstable val icon: ImageView
<runtime stability> = Unstable
}
unstable class AstridTaskAdapter { unstable class AstridTaskAdapter {
unstable val list: TaskListMetadata unstable val list: TaskListMetadata
runtime val filter: AstridOrderingFilter runtime val filter: AstridOrderingFilter
@ -267,7 +281,6 @@ unstable class NavigationDrawerAdapter {
unstable val colorProvider: ColorProvider unstable val colorProvider: ColorProvider
unstable val subheaderClickHandler: SubheaderClickHandler unstable val subheaderClickHandler: SubheaderClickHandler
stable var onClick: Function1<FilterListItem?, Unit> stable var onClick: Function1<FilterListItem?, Unit>
runtime var selected: Filter?
unstable val channel: Channel<List<FilterListItem>> unstable val channel: Channel<List<FilterListItem>>
unstable val updates: Queue<Pair<MutableList<FilterListItem>, DiffResult?>> unstable val updates: Queue<Pair<MutableList<FilterListItem>, DiffResult?>>
unstable val scope: CoroutineScope unstable val scope: CoroutineScope
@ -275,9 +288,6 @@ unstable class NavigationDrawerAdapter {
stable var dragging: Boolean stable var dragging: Boolean
<runtime stability> = Unstable <runtime stability> = Unstable
} }
stable class SeparatorViewHolder {
<runtime stability> = Stable
}
unstable class SubheaderClickHandler { unstable class SubheaderClickHandler {
unstable val activity: Activity unstable val activity: Activity
unstable val preferences: Preferences unstable val preferences: Preferences
@ -329,37 +339,34 @@ stable class BooleanCriterion {
unstable class CaldavFilter { unstable class CaldavFilter {
unstable val calendar: CaldavCalendar unstable val calendar: CaldavCalendar
stable val principals: Int stable val principals: Int
stable var count: Int stable val count: Int
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class CustomFilter { stable class CustomFilter {
stable val filter: Filter stable val filter: Filter
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class FilterImpl { stable class FilterImpl {
stable val title: String? stable val title: String?
stable val sql: String? stable val sql: String?
stable val valuesForNewTasks: String? stable val valuesForNewTasks: String?
stable val icon: Int stable val icon: Int
stable val tint: Int stable val tint: Int
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class GtasksFilter { unstable class GtasksFilter {
unstable val list: CaldavCalendar unstable val list: CaldavCalendar
stable var count: Int stable val count: Int
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class SearchFilter { stable class SearchFilter {
stable val title: String stable val title: String
stable val query: String stable val query: String
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class TagFilter { unstable class TagFilter {
unstable val tagData: TagData unstable val tagData: TagData
stable var count: Int stable val count: Int
stable var filterOverride: String? stable var filterOverride: String?
<runtime stability> = Unstable <runtime stability> = Unstable
} }
@ -758,7 +765,7 @@ unstable class FilterSettingsActivity {
unstable var nameLayout: TextInputLayout unstable var nameLayout: TextInputLayout
unstable var recyclerView: RecyclerView unstable var recyclerView: RecyclerView
unstable var fab: ExtendedFloatingActionButton unstable var fab: ExtendedFloatingActionButton
unstable var filter: CustomFilter? stable var filter: CustomFilter?
unstable var adapter: CustomFilterAdapter unstable var adapter: CustomFilterAdapter
unstable var criteria: MutableList<CriterionInstance> unstable var criteria: MutableList<CriterionInstance>
stable val defaultIcon: Int stable val defaultIcon: Int
@ -1277,6 +1284,24 @@ unstable class PurchaseText {
unstable val featureList: List<CarouselItem> unstable val featureList: List<CarouselItem>
<runtime stability> = Unstable <runtime stability> = Unstable
} }
stable class Filter {
stable val title: String
stable val icon: Int
stable val color: Int
stable val count: Int
stable val shareCount: Int
stable val selected: Boolean
stable val type: Function0<Filter>
<runtime stability> = Stable
}
stable class Header {
stable val title: String
stable val collapsed: Boolean
stable val hasError: Boolean
stable val canAdd: Boolean
stable val type: Function0<NavigationDrawerSubheader>
<runtime stability> = Stable
}
unstable class DashClockExtension { unstable class DashClockExtension {
unstable val job: CompletableJob unstable val job: CompletableJob
unstable val scope: CoroutineScope unstable val scope: CoroutineScope
@ -1907,7 +1932,6 @@ unstable class FilterCriteriaProvider {
} }
unstable class FilterProvider { unstable class FilterProvider {
unstable val context: Context unstable val context: Context
unstable val inventory: Inventory
unstable val builtInFilterExposer: BuiltInFilterExposer unstable val builtInFilterExposer: BuiltInFilterExposer
runtime val filterDao: FilterDao runtime val filterDao: FilterDao
stable val tagDataDao: TagDataDao stable val tagDataDao: TagDataDao
@ -1930,21 +1954,8 @@ unstable class LocationFilters {
unstable class MyTasksFilter { unstable class MyTasksFilter {
stable val title: String stable val title: String
stable var filterOverride: String? stable var filterOverride: String?
stable var count: Int
<runtime stability> = Unstable
}
unstable class NavigationDrawerAction {
stable val title: String
stable val icon: Int
stable val requestCode: Int
unstable val intent: Intent?
stable val itemType: Type
<runtime stability> = Unstable <runtime stability> = Unstable
} }
stable class NavigationDrawerSeparator {
stable val itemType: Type
<runtime stability> = Stable
}
unstable class NavigationDrawerSubheader { unstable class NavigationDrawerSubheader {
stable val title: String? stable val title: String?
stable val error: Boolean stable val error: Boolean
@ -1956,25 +1967,22 @@ unstable class NavigationDrawerSubheader {
stable val itemType: Type stable val itemType: Type
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class NotificationsFilter { stable class NotificationsFilter {
stable val title: String stable val title: String
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class PlaceFilter { stable class PlaceFilter {
stable val place: Place stable val place: Place
stable var count: Int stable val count: Int
<runtime stability> = Unstable <runtime stability> = Stable
} }
unstable class RecentlyModifiedFilter { stable class RecentlyModifiedFilter {
stable val title: String stable val title: String
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class SnoozedFilter { stable class SnoozedFilter {
stable val title: String stable val title: String
stable var count: Int <runtime stability> = Stable
<runtime stability> = Unstable
} }
unstable class TagFilters { unstable class TagFilters {
unstable var tagData: TagData unstable var tagData: TagData
@ -1984,7 +1992,6 @@ unstable class TagFilters {
unstable class TodayFilter { unstable class TodayFilter {
stable val title: String stable val title: String
stable var filterOverride: String? stable var filterOverride: String?
stable var count: Int
<runtime stability> = Unstable <runtime stability> = Unstable
} }
unstable class CommentBarFragment { unstable class CommentBarFragment {
@ -2857,7 +2864,7 @@ unstable class TagPickerActivity {
unstable var inventory: Inventory unstable var inventory: Inventory
unstable var colorProvider: ColorProvider unstable var colorProvider: ColorProvider
unstable val viewModel$delegate: Lazy<TagPickerViewModel> unstable val viewModel$delegate: Lazy<TagPickerViewModel>
unstable var taskIds: ArrayList<Long>? unstable var taskIds: ArrayList<Long>?{ kotlin.collections.TypeAliasesKt.ArrayList<Long>? }
unstable var editText: EditText unstable var editText: EditText
<runtime stability> = Unstable <runtime stability> = Unstable
} }
@ -3062,26 +3069,6 @@ unstable class OpenTask {
stable class ClearTaskEditFragment { stable class ClearTaskEditFragment {
<runtime stability> = Stable <runtime stability> = Stable
} }
unstable class NavigationDrawerFragment {
unstable var adapter: NavigationDrawerAdapter
unstable var preferences: Preferences
unstable var recyclerView: RecyclerView
unstable val viewModel$delegate: Lazy<NavigationDrawerViewModel>
<runtime stability> = Unstable
}
unstable class ViewState {
runtime val selected: Filter?
unstable val filters: List<FilterListItem>
<runtime stability> = Unstable
}
unstable class NavigationDrawerViewModel {
unstable val filterProvider: FilterProvider
stable val taskDao: TaskDao
unstable val localBroadcastManager: LocalBroadcastManager
unstable val _viewState: MutableStateFlow<ViewState>
stable val refreshReceiver: <no name provided>
<runtime stability> = Unstable
}
unstable class SubtaskControlSet { unstable class SubtaskControlSet {
unstable var activity: Activity unstable var activity: Activity
unstable var taskCompleter: TaskCompleter unstable var taskCompleter: TaskCompleter

@ -397,6 +397,42 @@ restartable skippable scheme("[androidx.compose.ui.UiComposable, [androidx.compo
stable content: Function2<Composer, Int, Unit> stable content: Function2<Composer, Int, Unit>
stable onClick: Function0<Unit>? = @static null stable onClick: Function0<Unit>? = @static null
) )
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun TaskListDrawer(
stable begForMoney: Boolean
filters: ImmutableList<DrawerItem>
stable onClick: Function1<DrawerItem, Unit>
stable onDrawerAction: Function1<DrawerAction, Unit>
stable onAddClick: Function1<Header, Unit>
stable onErrorClick: Function0<Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun FilterItem(
stable item: Filter
stable onClick: Function0<Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MenuAction(
stable icon: Int
stable title: Int
stable onClick: Function0<Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun DrawerIcon(
stable icon: Int
stable color: Int = @static 0
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HeaderItem(
stable item: Header
stable canAdd: Boolean
stable toggleCollapsed: Function0<Unit>
stable onAddClick: Function0<Unit>
stable onErrorClick: Function0<Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable, [androidx.compose.ui.UiComposable]]") fun MenuRow(
stable padding: PaddingValues? = @static PaddingValues(
horizontal = 16 . dp
)
stable onClick: Function0<Unit>
stable content: @[ExtensionFunctionType] Function3<RowScope, Composer, Int, Unit>
)
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MenuPreview()
restartable scheme("[androidx.compose.ui.UiComposable]") fun AlarmRow( restartable scheme("[androidx.compose.ui.UiComposable]") fun AlarmRow(
unstable vm: ReminderControlSetViewModel? = @dynamic viewModel(null, null, null, null, $composer, 0, 0b1111) unstable vm: ReminderControlSetViewModel? = @dynamic viewModel(null, null, null, null, $composer, 0, 0b1111)
stable permissionStatus: PermissionStatus stable permissionStatus: PermissionStatus

@ -1,25 +1,25 @@
{ {
"skippableComposables": 365, "skippableComposables": 387,
"restartableComposables": 489, "restartableComposables": 514,
"readonlyComposables": 0, "readonlyComposables": 0,
"totalComposables": 495, "totalComposables": 520,
"restartGroups": 489, "restartGroups": 514,
"totalGroups": 598, "totalGroups": 630,
"staticArguments": 762, "staticArguments": 792,
"certainArguments": 314, "certainArguments": 329,
"knownStableArguments": 4864, "knownStableArguments": 5083,
"knownUnstableArguments": 139, "knownUnstableArguments": 138,
"unknownStableArguments": 9, "unknownStableArguments": 11,
"totalArguments": 5012, "totalArguments": 5232,
"markedStableClasses": 0, "markedStableClasses": 0,
"inferredStableClasses": 100, "inferredStableClasses": 108,
"inferredUnstableClasses": 345, "inferredUnstableClasses": 334,
"inferredUncertainClasses": 1, "inferredUncertainClasses": 1,
"effectivelyStableClasses": 100, "effectivelyStableClasses": 108,
"totalClasses": 446, "totalClasses": 443,
"memoizedLambdas": 524, "memoizedLambdas": 549,
"singletonLambdas": 182, "singletonLambdas": 188,
"singletonComposableLambdas": 90, "singletonComposableLambdas": 94,
"composableLambdas": 223, "composableLambdas": 238,
"totalLambdas": 633 "totalLambdas": 667
} }

@ -578,6 +578,10 @@
++--- io.noties.markwon:ext-tasklist:4.6.2 ++--- io.noties.markwon:ext-tasklist:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*) +| \--- io.noties.markwon:core:4.6.2 (*)
++--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (*) ++--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (*)
++--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6
+| \--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.6
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -> 1.9.10 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0 -> 1.9.10
++--- com.squareup.okhttp3:okhttp:4.12.0 (*) ++--- com.squareup.okhttp3:okhttp:4.12.0 (*)
++--- com.github.franmontiel:PersistentCookieJar:1.0.1 ++--- com.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*) +| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*)

@ -793,6 +793,10 @@
++--- io.noties.markwon:ext-tasklist:4.6.2 ++--- io.noties.markwon:ext-tasklist:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*) +| \--- io.noties.markwon:core:4.6.2 (*)
++--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (*) ++--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10 (*)
++--- org.jetbrains.kotlinx:kotlinx-collections-immutable:0.3.6
+| \--- org.jetbrains.kotlinx:kotlinx-collections-immutable-jvm:0.3.6
+| +--- org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0 -> 1.9.10 (*)
+| \--- org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0 -> 1.9.10
++--- com.squareup.okhttp3:okhttp:4.12.0 (*) ++--- com.squareup.okhttp3:okhttp:4.12.0 (*)
++--- com.github.franmontiel:PersistentCookieJar:1.0.1 ++--- com.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*) +| \--- com.squareup.okhttp3:okhttp:3.1.2 -> 4.12.0 (*)

@ -126,6 +126,7 @@ gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
jchronic = { module = "com.rubiconproject.oss:jchronic", version.ref = "jchronic" } jchronic = { module = "com.rubiconproject.oss:jchronic", version.ref = "jchronic" }
junit = { module = "junit:junit", version.ref = "junit-junit" } junit = { module = "junit:junit", version.ref = "junit-junit" }
kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } kotlin-gradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlin-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.3.6" }
kotlin-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } kotlin-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-test" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinx-coroutines-test" }

Loading…
Cancel
Save