New drawer

pull/2584/head
Alex Baker 8 months ago
parent 7fd5647cb8
commit b5748aa8e6

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

@ -8,12 +8,23 @@ package com.todoroo.astrid.activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
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.repeatOnLifecycle
import com.google.android.material.composethemeadapter.MdcTheme
import com.todoroo.andlib.utility.AndroidUtilities
import com.todoroo.astrid.activity.TaskEditFragment.Companion.newTaskEditFragment
import com.todoroo.astrid.activity.TaskListFragment.TaskListFragmentCallbackHandler
import com.todoroo.astrid.adapter.SubheaderClickHandler
import com.todoroo.astrid.api.Filter
import com.todoroo.astrid.dao.TaskDao
import com.todoroo.astrid.data.Task
@ -29,20 +40,32 @@ import kotlinx.coroutines.withContext
import org.tasks.BuildConfig
import org.tasks.LocalBroadcastManager
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.analytics.Firebase
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.LocationDao
import org.tasks.data.Place
import org.tasks.data.TagDataDao
import org.tasks.databinding.TaskListActivityBinding
import org.tasks.dialogs.NewFilterDialog
import org.tasks.dialogs.WhatsNewDialog
import org.tasks.extensions.Context.nightMode
import org.tasks.extensions.Context.openUri
import org.tasks.extensions.hideKeyboard
import org.tasks.filters.FilterProvider
import org.tasks.filters.PlaceFilter
import org.tasks.intents.TaskIntents.getTaskListIntent
import org.tasks.location.LocationPickerActivity
import org.tasks.preferences.DefaultFilterProvider
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences
import org.tasks.themes.ColorProvider
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.MainActivityEvent
import org.tasks.ui.MainActivityEventBus
import org.tasks.ui.NavigationDrawerFragment
import org.tasks.ui.NavigationDrawerFragment.Companion.newNavigationDrawer
import timber.log.Timber
import javax.inject.Inject
@ -71,14 +92,23 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
@Inject lateinit var eventBus: MainActivityEventBus
@Inject lateinit var firebase: Firebase
private val viewModel: MainActivityViewModel by viewModels()
private var currentNightMode = 0
private var currentPro = false
private var filter: Filter? = null
private var actionMode: ActionMode? = null
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
*/
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
theme.applyTheme(this)
@ -86,15 +116,112 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
currentPro = inventory.hasPro
binding = TaskListActivityBinding.inflate(layoutInflater)
setContentView(binding.root)
if (savedInstanceState != null) {
filter = savedInstanceState.getParcelable(EXTRA_FILTER)
applyTheme()
}
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
.onEach(this::process)
.launchIn(lifecycleScope)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
applyTheme()
}
}
}
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?) {
when (requestCode) {
NavigationDrawerFragment.REQUEST_SETTINGS -> recreate()
NavigationDrawerFragment.REQUEST_NEW_LIST ->
REQUEST_NEW_LIST ->
if (resultCode == RESULT_OK) {
data
?.getParcelableExtra<Filter>(OPEN_FILTER)
?.let { startActivity(getTaskListIntent(this, it)) }
}
NavigationDrawerFragment.REQUEST_NEW_PLACE ->
REQUEST_NEW_PLACE ->
if (resultCode == RESULT_OK) {
data
?.getParcelableExtra<Place>(LocationPickerActivity.EXTRA_PLACE)
@ -130,14 +256,9 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
handleIntent()
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putParcelable(EXTRA_FILTER, filter)
}
private fun clearUi() {
finishActionMode()
navigationDrawer?.dismiss()
viewModel.setDrawerOpen(false)
}
private suspend fun getTaskToLoad(filter: Filter?): Task? {
@ -274,8 +395,10 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
}
private fun setFilter(newFilter: Filter?) {
filter = newFilter
applyTheme()
newFilter?.let {
viewModel.setFilter(it)
applyTheme()
}
}
private fun openTaskListFragment(filter: Filter?, force: Boolean = false) {
@ -291,8 +414,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
if (!force && filter == newFilter) {
return
}
filter = newFilter
defaultFilterProvider.lastViewedFilter = newFilter
viewModel.setFilter(newFilter)
applyTheme()
supportFragmentManager
.beginTransaction()
@ -308,10 +430,8 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
}
private val filterColor: ThemeColor
get() = if (filter != null && filter!!.tint != 0) colorProvider.getThemeColor(filter!!.tint, true) else theme.themeColor
private val navigationDrawer: NavigationDrawerFragment?
get() = supportFragmentManager.findFragmentByTag(FRAG_TAG_NAV_DRAWER) as? NavigationDrawerFragment
get() = filter?.tint?.takeIf { it != 0 }
?.let { colorProvider.getThemeColor(it, true) } ?: theme.themeColor
override fun onResume() {
super.onResume()
@ -362,7 +482,7 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
override fun onNavigationIconClicked() {
hideKeyboard()
newNavigationDrawer(filter).show(supportFragmentManager, FRAG_TAG_NAV_DRAWER)
viewModel.setDrawerOpen(true)
}
private val taskListFragment: TaskListFragment?
@ -413,10 +533,10 @@ class MainActivity : AppCompatActivity(), TaskListFragmentCallbackHandler {
const val FINISH_AFFINITY = "finish_affinity"
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_NAV_DRAWER = "frag_tag_nav_drawer"
private const val EXTRA_FILTER = "extra_filter"
private const val FLAG_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?
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 org.tasks.activities.DragAndDropDiffer
import org.tasks.billing.Inventory
import org.tasks.filters.NavigationDrawerAction
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.themes.ColorProvider
import java.util.LinkedList
@ -38,7 +37,6 @@ class NavigationDrawerAdapter @Inject constructor(
DragAndDropDiffer<FilterListItem, MutableList<FilterListItem>> {
private lateinit var onClick: (FilterListItem?) -> Unit
private var selected: Filter? = null
override val channel = Channel<List<FilterListItem>>(Channel.UNLIMITED)
override val updates: Queue<Pair<MutableList<FilterListItem>, DiffUtil.DiffResult?>> = LinkedList()
override val scope: CoroutineScope =
@ -58,11 +56,6 @@ class NavigationDrawerAdapter @Inject constructor(
override fun getItemCount() = items.size
fun setSelected(selected: Filter?) {
this.selected = selected
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val type = FilterListItem.Type.values()[viewType]
val view = LayoutInflater.from(parent.context).inflate(type.layout, parent, false)
@ -70,20 +63,17 @@ class NavigationDrawerAdapter @Inject constructor(
FilterListItem.Type.ITEM -> FilterViewHolder(
view, true, locale, activity, inventory, colorProvider) { onClickFilter(it) }
FilterListItem.Type.SUBHEADER -> SubheaderViewHolder(view, subheaderClickHandler)
FilterListItem.Type.ACTION -> ActionViewHolder(activity, view) { onClickFilter(it) }
else -> SeparatorViewHolder(view)
}
}
private fun onClickFilter(filter: FilterListItem?) =
onClick(if (filter == selected) null else filter)
onClick(filter)
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = getItem(position)
when (item.itemType) {
FilterListItem.Type.ITEM ->
(holder as FilterViewHolder).bind(item as Filter, item == selected, max(item.count, 0))
FilterListItem.Type.ACTION -> (holder as ActionViewHolder).bind(item as NavigationDrawerAction)
(holder as FilterViewHolder).bind(item as Filter, false, max(item.count, 0))
FilterListItem.Type.SUBHEADER ->
(holder as SubheaderViewHolder).bind((item as NavigationDrawerSubheader))
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.data.CaldavDao
import org.tasks.dialogs.NewFilterDialog
import org.tasks.filters.FilterProvider
import org.tasks.filters.NavigationDrawerSubheader
import org.tasks.filters.NavigationDrawerSubheader.SubheaderType.CALDAV
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.preferences.MainPreferences
import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject
class SubheaderClickHandler @Inject constructor(
@ -42,7 +42,7 @@ class SubheaderClickHandler @Inject constructor(
override fun onAdd(subheader: NavigationDrawerSubheader) {
when (subheader.addIntentRc) {
NavigationDrawerFragment.REQUEST_NEW_FILTER ->
FilterProvider.REQUEST_NEW_FILTER ->
NewFilterDialog.newFilterDialog().show(
(activity as AppCompatActivity).supportFragmentManager,
FRAG_TAG_NEW_FILTER
@ -55,6 +55,6 @@ class SubheaderClickHandler @Inject constructor(
activity.startActivity(Intent(activity, MainPreferences::class.java))
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(
val calendar: CaldavCalendar,
val principals: Int = 0,
override var count: Int = NO_COUNT,
override val count: Int = NO_COUNT,
) : Filter {
override val title: String?
get() = calendar.name

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

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

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

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

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

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

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

@ -100,11 +100,6 @@ class NavigationDrawerCustomization : ThemedInjectingAppCompatActivity(), Toolba
private fun updateFilters() = lifecycleScope.launch {
filterProvider
.drawerCustomizationItems()
.onEach { f ->
if (f is Filter) {
f.count = 0
}
}
.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.os.Bundle
import androidx.fragment.app.DialogFragment
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.api.CustomFilterCriterion
import com.todoroo.astrid.core.CriterionInstance
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.activities.FilterSettingsActivity
import org.tasks.filters.FilterCriteriaProvider
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject
@AndroidEntryPoint
@ -123,7 +123,7 @@ class NewFilterDialog : DialogFragment() {
intent.putExtra(FilterSettingsActivity.EXTRA_TITLE, title)
intent.putExtra(FilterSettingsActivity.EXTRA_CRITERIA, serialize(list))
}
activity?.startActivityForResult(intent, NavigationDrawerFragment.REQUEST_NEW_LIST)
activity?.startActivityForResult(intent, MainActivity.REQUEST_NEW_LIST)
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 principals: Int,
) {
fun toCaldavFilter(): CaldavFilter {
val filter = CaldavFilter(
calendar = caldavCalendar,
principals = principals,
)
filter.count = count
return filter
}
fun toCaldavFilter(): CaldavFilter = CaldavFilter(
calendar = caldavCalendar,
principals = principals,
count = count,
)
}

@ -2,6 +2,7 @@ package org.tasks.filters
import android.content.Context
import android.content.Intent
import com.todoroo.astrid.activity.MainActivity
import com.todoroo.astrid.api.CustomFilter
import com.todoroo.astrid.api.Filter
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 org.tasks.BuildConfig
import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.activities.GoogleTaskListSettingsActivity
import org.tasks.activities.NavigationDrawerCustomization
import org.tasks.activities.TagSettingsActivity
import org.tasks.billing.Inventory
import org.tasks.caldav.BaseCaldavCalendarSettingsActivity
import org.tasks.data.CaldavAccount
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.filters.NavigationDrawerSubheader.SubheaderType
import org.tasks.location.LocationPickerActivity
import org.tasks.preferences.HelpAndFeedback
import org.tasks.preferences.MainPreferences
import org.tasks.preferences.Preferences
import org.tasks.ui.NavigationDrawerFragment
import javax.inject.Inject
class FilterProvider @Inject constructor(
@param:ApplicationContext private val context: Context,
private val inventory: Inventory,
private val builtInFilterExposer: BuiltInFilterExposer,
private val filterDao: FilterDao,
private val tagDataDao: TagDataDao,
private val googleTaskListDao: GoogleTaskListDao,
private val caldavDao: CaldavDao,
private val preferences: Preferences,
private val locationDao: LocationDao) {
private val locationDao: LocationDao
) {
suspend fun listPickerItems(): List<FilterListItem> =
caldavFilters(false)
suspend fun navDrawerItems(): List<FilterListItem> =
getAllFilters(hideUnused = true).plus(navDrawerFooter)
suspend fun drawerItems(): List<FilterListItem> =
getAllFilters(showCreate = true)
suspend fun filterPickerItems(): List<FilterListItem> =
getAllFilters(showCreate = false)
@ -95,7 +89,7 @@ class FilterProvider @Inject constructor(
collapsed,
SubheaderType.PREFERENCE,
R.string.p_collapse_filters.toLong(),
NavigationDrawerFragment.REQUEST_NEW_FILTER,
REQUEST_NEW_FILTER,
if (showCreate) Intent() else null))
.apply { if (collapsed) return this }
.plusAllIf(showBuiltIn) {
@ -116,7 +110,7 @@ class FilterProvider @Inject constructor(
collapsed,
SubheaderType.PREFERENCE,
R.string.p_collapse_tags.toLong(),
NavigationDrawerFragment.REQUEST_NEW_LIST,
MainActivity.REQUEST_NEW_LIST,
if (showCreate) {
Intent(context, TagSettingsActivity::class.java)
} else {
@ -143,7 +137,7 @@ class FilterProvider @Inject constructor(
collapsed,
SubheaderType.PREFERENCE,
R.string.p_collapse_locations.toLong(),
NavigationDrawerFragment.REQUEST_NEW_PLACE,
MainActivity.REQUEST_NEW_PLACE,
if (showCreate) {
Intent(context, LocationPickerActivity::class.java)
} else {
@ -176,45 +170,6 @@ class FilterProvider @Inject constructor(
.toList()
.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> =
listOf(
NavigationDrawerSubheader(
@ -223,7 +178,7 @@ class FilterProvider @Inject constructor(
account.isCollapsed,
SubheaderType.GOOGLE_TASKS,
account.id,
NavigationDrawerFragment.REQUEST_NEW_LIST,
MainActivity.REQUEST_NEW_LIST,
if (showCreate) {
Intent(context, GoogleTaskListSettingsActivity::class.java)
.putExtra(GoogleTaskListSettingsActivity.EXTRA_ACCOUNT, account)
@ -267,7 +222,7 @@ class FilterProvider @Inject constructor(
else -> SubheaderType.CALDAV
},
account.id,
NavigationDrawerFragment.REQUEST_NEW_LIST,
MainActivity.REQUEST_NEW_LIST,
if (showCreate) {
Intent(context, account.listSettingsClass())
.putExtra(
@ -285,6 +240,7 @@ class FilterProvider @Inject constructor(
.sort())
companion object {
const val REQUEST_NEW_FILTER = 101015
private val COMPARATOR = Comparator<Filter> { f1, f2 ->
when {
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> =
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> =
if (predicate) filter(predicate2) else this
}

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

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

@ -3,7 +3,6 @@ package org.tasks.filters
import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.QueryTemplate
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.data.Task
import kotlinx.parcelize.Parcelize
@ -14,7 +13,6 @@ import org.tasks.themes.CustomIcons
data class MyTasksFilter(
override val title: String,
override var filterOverride: String? = null,
override var count: Int = NO_COUNT,
) : AstridOrderingFilter {
override val icon: Int
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.QueryTemplate
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.data.Task
import kotlinx.parcelize.Parcelize
@ -13,7 +12,6 @@ import org.tasks.notifications.Notification
@Parcelize
data class NotificationsFilter(
override val title: String,
override var count: Int = NO_COUNT,
) : Filter {
override val icon: Int
get() = R.drawable.ic_outline_notifications_24px

@ -19,7 +19,7 @@ import org.tasks.themes.CustomIcons
@Parcelize
data class PlaceFilter(
val place: Place,
override var count: Int = NO_COUNT,
override val count: Int = NO_COUNT,
) : Filter {
override val valuesForNewTasks: String
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.QueryTemplate
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.data.Task
import kotlinx.parcelize.Parcelize
@ -14,7 +13,6 @@ import org.tasks.time.DateTime
@Parcelize
data class RecentlyModifiedFilter(
override val title: String,
override var count: Int = NO_COUNT,
) : Filter {
override val icon: Int
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.QueryTemplate
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.data.Task
import kotlinx.parcelize.Parcelize
@ -15,7 +14,6 @@ import org.tasks.data.TaskDao.TaskCriteria.activeAndVisible
@Parcelize
data class SnoozedFilter(
override val title: String,
override var count: Int = NO_COUNT,
) : Filter {
override val icon: Int
get() = R.drawable.ic_snooze_white_24dp

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

@ -4,7 +4,6 @@ import com.todoroo.andlib.sql.Criterion
import com.todoroo.andlib.sql.QueryTemplate
import com.todoroo.andlib.utility.AndroidUtilities
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.PermaSql
import com.todoroo.astrid.data.Task
@ -16,7 +15,6 @@ import org.tasks.themes.CustomIcons
data class TodayFilter(
override val title: String,
override var filterOverride: String? = null,
override var count: Int = NO_COUNT,
) : AstridOrderingFilter {
override val sql: String
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.ThemeColor
import org.tasks.themes.ThemeColor.getLauncherColor
import org.tasks.ui.NavigationDrawerFragment.Companion.REQUEST_PURCHASE
import java.util.Locale
import javax.inject.Inject
@ -246,6 +245,7 @@ class LookAndFeel : InjectingPreferenceFragment() {
private const val REQUEST_ACCENT_PICKER = 10003
private const val REQUEST_LAUNCHER_PICKER = 10004
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_THEME_PICKER = "frag_tag_theme_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>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</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"
tools:ignore="InconsistentLayout" />
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

@ -264,19 +264,4 @@
<item name="hintTextColor">?attr/colorSecondary</item>
</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>

@ -148,11 +148,33 @@ unstable class MainActivity {
runtime var alarmDao: AlarmDao
unstable var eventBus: MutableSharedFlow<MainActivityEvent>{ org.tasks.ui.MainActivityEventBus }
unstable var firebase: Firebase
unstable val viewModel$delegate: Lazy<MainActivityViewModel>
stable var currentNightMode: Int
stable var currentPro: Boolean
runtime var filter: Filter?
unstable var actionMode: ActionMode?
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
}
unstable class ShareLinkActivity {
@ -222,14 +244,6 @@ unstable class TaskListFragment {
unstable val listSettingsRequest: ActivityResultLauncher<@[FlexibleNullability] Intent?>
<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 val list: TaskListMetadata
runtime val filter: AstridOrderingFilter
@ -267,7 +281,6 @@ unstable class NavigationDrawerAdapter {
unstable val colorProvider: ColorProvider
unstable val subheaderClickHandler: SubheaderClickHandler
stable var onClick: Function1<FilterListItem?, Unit>
runtime var selected: Filter?
unstable val channel: Channel<List<FilterListItem>>
unstable val updates: Queue<Pair<MutableList<FilterListItem>, DiffResult?>>
unstable val scope: CoroutineScope
@ -275,9 +288,6 @@ unstable class NavigationDrawerAdapter {
stable var dragging: Boolean
<runtime stability> = Unstable
}
stable class SeparatorViewHolder {
<runtime stability> = Stable
}
unstable class SubheaderClickHandler {
unstable val activity: Activity
unstable val preferences: Preferences
@ -329,37 +339,34 @@ stable class BooleanCriterion {
unstable class CaldavFilter {
unstable val calendar: CaldavCalendar
stable val principals: Int
stable var count: Int
stable val count: Int
<runtime stability> = Unstable
}
unstable class CustomFilter {
stable class CustomFilter {
stable val filter: Filter
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class FilterImpl {
stable class FilterImpl {
stable val title: String?
stable val sql: String?
stable val valuesForNewTasks: String?
stable val icon: Int
stable val tint: Int
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class GtasksFilter {
unstable val list: CaldavCalendar
stable var count: Int
stable val count: Int
<runtime stability> = Unstable
}
unstable class SearchFilter {
stable class SearchFilter {
stable val title: String
stable val query: String
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class TagFilter {
unstable val tagData: TagData
stable var count: Int
stable val count: Int
stable var filterOverride: String?
<runtime stability> = Unstable
}
@ -758,7 +765,7 @@ unstable class FilterSettingsActivity {
unstable var nameLayout: TextInputLayout
unstable var recyclerView: RecyclerView
unstable var fab: ExtendedFloatingActionButton
unstable var filter: CustomFilter?
stable var filter: CustomFilter?
unstable var adapter: CustomFilterAdapter
unstable var criteria: MutableList<CriterionInstance>
stable val defaultIcon: Int
@ -1277,6 +1284,24 @@ unstable class PurchaseText {
unstable val featureList: List<CarouselItem>
<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 val job: CompletableJob
unstable val scope: CoroutineScope
@ -1907,7 +1932,6 @@ unstable class FilterCriteriaProvider {
}
unstable class FilterProvider {
unstable val context: Context
unstable val inventory: Inventory
unstable val builtInFilterExposer: BuiltInFilterExposer
runtime val filterDao: FilterDao
stable val tagDataDao: TagDataDao
@ -1930,21 +1954,8 @@ unstable class LocationFilters {
unstable class MyTasksFilter {
stable val title: 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
}
stable class NavigationDrawerSeparator {
stable val itemType: Type
<runtime stability> = Stable
}
unstable class NavigationDrawerSubheader {
stable val title: String?
stable val error: Boolean
@ -1956,25 +1967,22 @@ unstable class NavigationDrawerSubheader {
stable val itemType: Type
<runtime stability> = Unstable
}
unstable class NotificationsFilter {
stable class NotificationsFilter {
stable val title: String
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class PlaceFilter {
stable class PlaceFilter {
stable val place: Place
stable var count: Int
<runtime stability> = Unstable
stable val count: Int
<runtime stability> = Stable
}
unstable class RecentlyModifiedFilter {
stable class RecentlyModifiedFilter {
stable val title: String
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class SnoozedFilter {
stable class SnoozedFilter {
stable val title: String
stable var count: Int
<runtime stability> = Unstable
<runtime stability> = Stable
}
unstable class TagFilters {
unstable var tagData: TagData
@ -1984,7 +1992,6 @@ unstable class TagFilters {
unstable class TodayFilter {
stable val title: String
stable var filterOverride: String?
stable var count: Int
<runtime stability> = Unstable
}
unstable class CommentBarFragment {
@ -2857,7 +2864,7 @@ unstable class TagPickerActivity {
unstable var inventory: Inventory
unstable var colorProvider: ColorProvider
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
<runtime stability> = Unstable
}
@ -3062,26 +3069,6 @@ unstable class OpenTask {
stable class ClearTaskEditFragment {
<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 var activity: Activity
unstable var taskCompleter: TaskCompleter

@ -397,6 +397,42 @@ restartable skippable scheme("[androidx.compose.ui.UiComposable, [androidx.compo
stable content: Function2<Composer, Int, Unit>
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(
unstable vm: ReminderControlSetViewModel? = @dynamic viewModel(null, null, null, null, $composer, 0, 0b1111)
stable permissionStatus: PermissionStatus

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

@ -578,6 +578,10 @@
++--- io.noties.markwon:ext-tasklist:4.6.2
+| \--- io.noties.markwon:core:4.6.2 (*)
++--- 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.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- 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:core:4.6.2 (*)
++--- 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.github.franmontiel:PersistentCookieJar:1.0.1
+| \--- 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" }
junit = { module = "junit:junit", version.ref = "junit-junit" }
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-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" }

Loading…
Cancel
Save