From 840049b206a4d73e3924437e03fea598941edd11 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Sun, 23 Feb 2025 08:35:53 -0600 Subject: [PATCH] Refactoring TaskEditControlFragments --- app/build.gradle.kts | 1 + .../todoroo/astrid/files/FilesControlSet.kt | 66 ++++---- .../astrid/repeats/RepeatControlSet.kt | 57 +++---- .../com/todoroo/astrid/tags/TagsControlSet.kt | 38 ++--- .../todoroo/astrid/timers/TimerControlSet.kt | 40 ++--- .../todoroo/astrid/ui/ReminderControlSet.kt | 157 +++++++++--------- .../todoroo/astrid/ui/StartDateControlSet.kt | 91 +++++----- .../java/org/tasks/ui/CalendarControlSet.kt | 64 ++++--- .../java/org/tasks/ui/LocationControlSet.kt | 98 ++++++----- .../java/org/tasks/ui/SubtaskControlSet.kt | 92 +++++----- .../org/tasks/ui/TaskEditControlFragment.kt | 20 +-- .../java/org/tasks/ui/TaskEditViewModel.kt | 2 +- gradle/libs.versions.toml | 1 + 13 files changed, 338 insertions(+), 389 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3e6210717..95f0bbef0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -177,6 +177,7 @@ dependencies { implementation(libs.dagger.hilt) ksp(libs.dagger.hilt.compiler) ksp(libs.androidx.hilt.compiler) + implementation(libs.androidx.hilt.navigation) implementation(libs.androidx.hilt.work) implementation(libs.androidx.datastore) diff --git a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt index f58078593..e36686347 100644 --- a/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/files/FilesControlSet.kt @@ -8,10 +8,9 @@ package com.todoroo.astrid.files import android.app.Activity import android.content.Intent import android.net.Uri -import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint @@ -33,43 +32,38 @@ import javax.inject.Inject class FilesControlSet : TaskEditControlFragment() { @Inject lateinit var taskAttachmentDao: TaskAttachmentDao @Inject lateinit var preferences: Preferences - - override fun createView(savedInstanceState: Bundle?) { - val task = viewModel.viewState.value.task - if (savedInstanceState == null) { - if (task.hasTransitory(TaskAttachment.KEY)) { - for (uri in (task.getTransitory>(TaskAttachment.KEY))!!) { + + @Composable + override fun Content() { + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + LaunchedEffect(Unit) { + if (viewState.task.hasTransitory(TaskAttachment.KEY)) { + for (uri in (viewState.task.getTransitory>(TaskAttachment.KEY))!!) { newAttachment(uri) } } } - } - - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - AttachmentRow( - attachments = viewState.attachments, - openAttachment = { - Timber.d("Clicked open $it") - FileHelper.startActionView( - context, - if (Strings.isNullOrEmpty(it.uri)) null else Uri.parse(it.uri) - ) - }, - deleteAttachment = { - Timber.d("Clicked delete $it") - viewModel.setAttachments(viewState.attachments - it) - }, - addAttachment = { - Timber.d("Add attachment clicked") - AddAttachmentDialog.newAddAttachmentDialog(this@FilesControlSet) - .show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG) - }, + val context = LocalContext.current + AttachmentRow( + attachments = viewState.attachments, + openAttachment = { + Timber.d("Clicked open $it") + FileHelper.startActionView( + context, + if (Strings.isNullOrEmpty(it.uri)) null else Uri.parse(it.uri) ) - } - } + }, + deleteAttachment = { + Timber.d("Clicked delete $it") + viewModel.setAttachments(viewState.attachments - it) + }, + addAttachment = { + Timber.d("Add attachment clicked") + AddAttachmentDialog.newAddAttachmentDialog(this@FilesControlSet) + .show(parentFragmentManager, FRAG_TAG_ADD_ATTACHMENT_DIALOG) + }, + ) + } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) { diff --git a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt index 09be98832..5ce246bf0 100644 --- a/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/repeats/RepeatControlSet.kt @@ -7,12 +7,9 @@ package com.todoroo.astrid.repeats import android.app.Activity.RESULT_OK import android.content.Intent -import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.WeekDay @@ -67,36 +64,30 @@ class RepeatControlSet : TaskEditControlFragment() { } } - override fun createView(savedInstanceState: Bundle?) { - lifecycleScope.launchWhenResumed { - viewModel.dueDate.collect { - onDueDateChanged() - } + @Composable + override fun Content() { + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + val dueDate = viewModel.dueDate.collectAsStateWithLifecycle().value + LaunchedEffect(dueDate) { + onDueDateChanged() } - } - - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - RepeatRow( - recurrence = viewState.task.recurrence?.let { repeatRuleToString.toString(it) }, - repeatFrom = viewState.task.repeatFrom, - onClick = { - val accountType = viewState.list.account.accountType - BasicRecurrenceDialog.newBasicRecurrenceDialog( - target = this@RepeatControlSet, - rc = REQUEST_RECURRENCE, - rrule = viewState.task.recurrence, - dueDate = viewModel.dueDate.value, - accountType = accountType, - ) - .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE) - }, - onRepeatFromChanged = { viewModel.setRepeatFrom(it) } + RepeatRow( + recurrence = viewState.task.recurrence?.let { repeatRuleToString.toString(it) }, + repeatFrom = viewState.task.repeatFrom, + onClick = { + val accountType = viewState.list.account.accountType + BasicRecurrenceDialog.newBasicRecurrenceDialog( + target = this@RepeatControlSet, + rc = REQUEST_RECURRENCE, + rrule = viewState.task.recurrence, + dueDate = dueDate, + accountType = accountType, ) - } - } + .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE) + }, + onRepeatFromChanged = { viewModel.setRepeatFrom(it) } + ) + } companion object { val TAG = R.string.TEA_ctrl_repeat_pref diff --git a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt index b6d31bf14..deddd9f4f 100644 --- a/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/tags/TagsControlSet.kt @@ -2,9 +2,7 @@ package com.todoroo.astrid.tags import android.app.Activity import android.content.Intent -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable import androidx.core.content.IntentCompat import androidx.lifecycle.compose.collectAsStateWithLifecycle import dagger.hilt.android.AndroidEntryPoint @@ -22,25 +20,23 @@ import javax.inject.Inject class TagsControlSet : TaskEditControlFragment() { @Inject lateinit var chipProvider: ChipProvider - private fun onRowClick() { - } - - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - TagsRow( - tags = viewState.tags, - colorProvider = { chipProvider.getColor(it) }, - onClick = { - val intent = Intent(context, TagPickerActivity::class.java) - intent.putParcelableArrayListExtra(TagPickerActivity.EXTRA_SELECTED, ArrayList(viewState.tags)) - startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY) - }, - onClear = { viewModel.setTags(viewState.tags.minus(it)) }, + @Composable + override fun Content() { + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + TagsRow( + tags = viewState.tags, + colorProvider = { chipProvider.getColor(it) }, + onClick = { + val intent = Intent(context, TagPickerActivity::class.java) + intent.putParcelableArrayListExtra( + TagPickerActivity.EXTRA_SELECTED, + ArrayList(viewState.tags) ) - } - } + startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY) + }, + onClear = { viewModel.setTags(viewState.tags.minus(it)) }, + ) + } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) { diff --git a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt index 2ef0e9560..f70141951 100644 --- a/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/timers/TimerControlSet.kt @@ -6,12 +6,11 @@ package com.todoroo.astrid.timers import android.app.Activity -import android.os.Bundle import android.text.format.DateUtils import android.view.View -import android.view.ViewGroup import androidx.appcompat.app.AlertDialog -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.todoroo.astrid.ui.TimeDurationControlSet @@ -45,14 +44,6 @@ class TimerControlSet : TaskEditControlFragment() { private var dialog: AlertDialog? = null private lateinit var dialogView: View - override fun createView(savedInstanceState: Bundle?) { - dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null) - estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme) - elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme) - estimated.setTimeDuration(viewModel.estimatedSeconds.value) - elapsed.setTimeDuration(viewModel.elapsedSeconds.value) - } - private fun onRowClick() { if (dialog == null) { dialog = buildDialog() @@ -86,18 +77,23 @@ class TimerControlSet : TaskEditControlFragment() { } } - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - TimerRow( - started = viewModel.timerStarted.collectAsStateWithLifecycle().value, - estimated = viewModel.estimatedSeconds.collectAsStateWithLifecycle().value, - elapsed = viewModel.elapsedSeconds.collectAsStateWithLifecycle().value, - timerClicked = this@TimerControlSet::timerClicked, - onClick = this@TimerControlSet::onRowClick, - ) - } + @Composable + override fun Content() { + LaunchedEffect(Unit) { + dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null) + estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme) + elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme) + estimated.setTimeDuration(viewModel.estimatedSeconds.value) + elapsed.setTimeDuration(viewModel.elapsedSeconds.value) } + TimerRow( + started = viewModel.timerStarted.collectAsStateWithLifecycle().value, + estimated = viewModel.estimatedSeconds.collectAsStateWithLifecycle().value, + elapsed = viewModel.elapsedSeconds.collectAsStateWithLifecycle().value, + timerClicked = this@TimerControlSet::timerClicked, + onClick = this@TimerControlSet::onRowClick, + ) + } private fun timerActive() = viewModel.timerStarted.value > 0 diff --git a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt index 17251ca92..cd01bed70 100644 --- a/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/ui/ReminderControlSet.kt @@ -5,15 +5,14 @@ import android.app.Activity import android.app.Activity.RESULT_OK import android.content.DialogInterface import android.content.Intent -import android.os.Bundle -import android.view.View -import android.view.ViewGroup import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus @@ -39,94 +38,90 @@ class ReminderControlSet : TaskEditControlFragment() { @Inject lateinit var activity: Activity @Inject lateinit var dialogBuilder: DialogBuilder - private val ringMode = mutableStateOf(0) - - override fun createView(savedInstanceState: Bundle?) { - when { - viewModel.ringNonstop -> setRingMode(2) - viewModel.ringFiveTimes -> setRingMode(1) - else -> setRingMode(0) - } - } - - private fun onClickRingType() { - val modes = resources.getStringArray(R.array.reminder_ring_modes) - val ringMode = when { - viewModel.ringNonstop -> 2 - viewModel.ringFiveTimes -> 1 - else -> 0 - } - dialogBuilder - .newDialog() - .setSingleChoiceItems(modes, ringMode) { dialog: DialogInterface, which: Int -> - setRingMode(which) - dialog.dismiss() - } - .show() - } + private val ringMode = mutableIntStateOf(0) private fun setRingMode(ringMode: Int) { viewModel.ringNonstop = ringMode == 2 viewModel.ringFiveTimes = ringMode == 1 - this.ringMode.value = ringMode + this.ringMode.intValue = ringMode } @OptIn(ExperimentalPermissionsApi::class) - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val ringMode by remember { this@ReminderControlSet.ringMode } - val hasReminderPermissions by rememberReminderPermissionState() - val notificationPermissions = if (AndroidUtilities.atLeastTiramisu()) { - rememberPermissionState( - Manifest.permission.POST_NOTIFICATIONS, - onPermissionResult = { success -> - if (success) { - NotificationSchedulerIntentService.enqueueWork(context) - } - } - ) + @Composable + override fun Content() { + LaunchedEffect(Unit) { + when { + viewModel.ringNonstop -> setRingMode(2) + viewModel.ringFiveTimes -> setRingMode(1) + else -> setRingMode(0) + } + } + val ringMode by remember { this@ReminderControlSet.ringMode } + val hasReminderPermissions by rememberReminderPermissionState() + val notificationPermissions = if (AndroidUtilities.atLeastTiramisu()) { + rememberPermissionState( + Manifest.permission.POST_NOTIFICATIONS, + onPermissionResult = { success -> + if (success) { + NotificationSchedulerIntentService.enqueueWork(context) + } + } + ) + } else { + null + } + val pickDateAndTime = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult + val data = result.data ?: return@rememberLauncherForActivityResult + val timestamp = + data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L) + val replace: Alarm? = data.getParcelableExtra(EXTRA_REPLACE) + replace?.let { viewModel.removeAlarm(it) } + viewModel.addAlarm(Alarm(time = timestamp, type = TYPE_DATE_TIME)) + } + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + val context = LocalContext.current + AlarmRow( + alarms = viewState.alarms, + hasNotificationPermissions = hasReminderPermissions && + (notificationPermissions == null || notificationPermissions.status == PermissionStatus.Granted), + fixNotificationPermissions = { + if (hasReminderPermissions) { + notificationPermissions?.launchPermissionRequest() } else { - null + context.openReminderSettings() } - val pickDateAndTime = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult - val data = result.data ?: return@rememberLauncherForActivityResult - val timestamp = - data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L) - val replace: Alarm? = data.getParcelableExtra(EXTRA_REPLACE) - replace?.let { viewModel.removeAlarm(it) } - viewModel.addAlarm(Alarm(time = timestamp, type = TYPE_DATE_TIME)) - } - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - AlarmRow( - alarms = viewState.alarms, - hasNotificationPermissions = hasReminderPermissions && - (notificationPermissions == null || notificationPermissions.status == PermissionStatus.Granted), - fixNotificationPermissions = { - if (hasReminderPermissions) { - notificationPermissions?.launchPermissionRequest() - } else { - context.openReminderSettings() - } - }, - ringMode = ringMode, - addAlarm = viewModel::addAlarm, - openRingType = this@ReminderControlSet::onClickRingType, - deleteAlarm = viewModel::removeAlarm, - pickDateAndTime = { replace -> - val timestamp = replace?.takeIf { it.type == TYPE_DATE_TIME }?.time - ?: DateTimeUtils.newDateTime().noon().millis - pickDateAndTime.launch( - Intent(activity, DateAndTimePickerActivity::class.java) - .putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, timestamp) - .putExtra(EXTRA_REPLACE, replace) - ) + }, + ringMode = ringMode, + addAlarm = viewModel::addAlarm, + openRingType = { + val modes = resources.getStringArray(R.array.reminder_ring_modes) + val selectedIndex = when { + viewModel.ringNonstop -> 2 + viewModel.ringFiveTimes -> 1 + else -> 0 + } + dialogBuilder + .newDialog() + .setSingleChoiceItems(modes, selectedIndex) { dialog: DialogInterface, which: Int -> + setRingMode(which) + dialog.dismiss() } + .show() + }, + deleteAlarm = viewModel::removeAlarm, + pickDateAndTime = { replace -> + val timestamp = replace?.takeIf { it.type == TYPE_DATE_TIME }?.time + ?: DateTimeUtils.newDateTime().noon().millis + pickDateAndTime.launch( + Intent(activity, DateAndTimePickerActivity::class.java) + .putExtra(DateAndTimePickerActivity.EXTRA_TIMESTAMP, timestamp) + .putExtra(EXTRA_REPLACE, replace) ) } - } + ) + } companion object { val TAG = R.string.TEA_ctrl_reminders_pref diff --git a/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt b/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt index 75303ac2a..087db0a91 100644 --- a/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt +++ b/app/src/main/java/com/todoroo/astrid/ui/StartDateControlSet.kt @@ -3,12 +3,10 @@ package com.todoroo.astrid.ui import android.app.Activity import android.content.Context import android.content.Intent -import android.os.Bundle -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.fragment.app.viewModels import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.lifecycleScope import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.runBlocking import org.tasks.R @@ -34,60 +32,55 @@ class StartDateControlSet : TaskEditControlFragment() { private val vm: StartDateViewModel by viewModels() - override fun createView(savedInstanceState: Bundle?) { - if (savedInstanceState == null) { + @Composable + override fun Content() { + LaunchedEffect(Unit) { vm.init( dueDate = viewModel.dueDate.value, startDate = viewModel.startDate.value, isNew = viewModel.viewState.value.isNew ) } - lifecycleScope.launchWhenResumed { - viewModel.dueDate.collect { - applySelected() + val dueDate = viewModel.dueDate.collectAsStateWithLifecycle().value + val selectedDay = vm.selectedDay.collectAsStateWithLifecycle().value + val selectedTime = vm.selectedTime.collectAsStateWithLifecycle().value + StartDateRow( + startDate = viewModel.startDate.collectAsStateWithLifecycle().value, + selectedDay = selectedDay, + selectedTime = selectedTime, + hasDueDate = dueDate > 0, + printDate = { + runBlocking { + getRelativeDateTime( + selectedDay + selectedTime, + requireContext().is24HourFormat, + DateStyle.FULL, + alwaysDisplayFullDate = preferences.alwaysDisplayFullDate + ) + } + }, + onClick = { + val fragmentManager = parentFragmentManager + if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) { + StartDatePicker.newDateTimePicker( + this@StartDateControlSet, + REQUEST_START_DATE, + vm.selectedDay.value, + vm.selectedTime.value, + preferences.getBoolean( + R.string.p_auto_dismiss_datetime_edit_screen, + false + ) + ) + .show(fragmentManager, FRAG_TAG_DATE_PICKER) + } } - } - } + ) - override fun bind(parent: ViewGroup?) = - (parent as ComposeView).apply { - setContent { - val selectedDay = vm.selectedDay.collectAsStateWithLifecycle().value - val selectedTime = vm.selectedTime.collectAsStateWithLifecycle().value - StartDateRow( - startDate = viewModel.startDate.collectAsStateWithLifecycle().value, - selectedDay = selectedDay, - selectedTime = selectedTime, - hasDueDate = viewModel.dueDate.collectAsStateWithLifecycle().value > 0, - printDate = { - runBlocking { - getRelativeDateTime( - selectedDay + selectedTime, - requireContext().is24HourFormat, - DateStyle.FULL, - alwaysDisplayFullDate = preferences.alwaysDisplayFullDate - ) - } - }, - onClick = { - val fragmentManager = parentFragmentManager - if (fragmentManager.findFragmentByTag(FRAG_TAG_DATE_PICKER) == null) { - StartDatePicker.newDateTimePicker( - this@StartDateControlSet, - REQUEST_START_DATE, - vm.selectedDay.value, - vm.selectedTime.value, - preferences.getBoolean( - R.string.p_auto_dismiss_datetime_edit_screen, - false - ) - ) - .show(fragmentManager, FRAG_TAG_DATE_PICKER) - } - } - ) - } + LaunchedEffect(dueDate) { + applySelected() } + } @Deprecated("Deprecated in Java") override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { diff --git a/app/src/main/java/org/tasks/ui/CalendarControlSet.kt b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt index fcdd6122e..553b5bc50 100644 --- a/app/src/main/java/org/tasks/ui/CalendarControlSet.kt +++ b/app/src/main/java/org/tasks/ui/CalendarControlSet.kt @@ -4,11 +4,9 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.provider.CalendarContract -import android.view.View -import android.view.ViewGroup import android.widget.Toast.LENGTH_SHORT +import androidx.compose.runtime.Composable import androidx.compose.runtime.remember -import androidx.compose.ui.platform.ComposeView import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.todoroo.astrid.activity.TaskEditFragment import dagger.hilt.android.AndroidEntryPoint @@ -42,38 +40,36 @@ class CalendarControlSet : TaskEditControlFragment() { } } - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - CalendarRow( - eventUri = viewModel.eventUri.collectAsStateWithLifecycle().value, - selectedCalendar = remember (viewState.calendar) { - calendarProvider.getCalendar(viewState.calendar)?.name - }, - onClick = { - if (viewModel.eventUri.value.isNullOrBlank()) { - CalendarPicker - .newCalendarPicker( - requireParentFragment(), - TaskEditFragment.REQUEST_CODE_PICK_CALENDAR, - viewState.calendar, - ) - .show( - requireParentFragment().parentFragmentManager, - TaskEditFragment.FRAG_TAG_CALENDAR_PICKER - ) - } else { - openCalendarEvent() - } - }, - clear = { - viewModel.setCalendar(null) - viewModel.eventUri.value = null - } - ) + @Composable + override fun Content() { + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + CalendarRow( + eventUri = viewModel.eventUri.collectAsStateWithLifecycle().value, + selectedCalendar = remember(viewState.calendar) { + calendarProvider.getCalendar(viewState.calendar)?.name + }, + onClick = { + if (viewModel.eventUri.value.isNullOrBlank()) { + CalendarPicker + .newCalendarPicker( + requireParentFragment(), + TaskEditFragment.REQUEST_CODE_PICK_CALENDAR, + viewState.calendar, + ) + .show( + requireParentFragment().parentFragmentManager, + TaskEditFragment.FRAG_TAG_CALENDAR_PICKER + ) + } else { + openCalendarEvent() + } + }, + clear = { + viewModel.setCalendar(null) + viewModel.eventUri.value = null } - } + ) + } private fun openCalendarEvent() { val cr = activity.contentResolver diff --git a/app/src/main/java/org/tasks/ui/LocationControlSet.kt b/app/src/main/java/org/tasks/ui/LocationControlSet.kt index 7f5d3d205..a691d721f 100644 --- a/app/src/main/java/org/tasks/ui/LocationControlSet.kt +++ b/app/src/main/java/org/tasks/ui/LocationControlSet.kt @@ -4,9 +4,7 @@ import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Parcelable -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable import androidx.core.content.IntentCompat import androidx.core.util.Pair import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -53,57 +51,55 @@ class LocationControlSet : TaskEditControlFragment() { } @OptIn(ExperimentalPermissionsApi::class) - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - val hasPermissions = - rememberMultiplePermissionsState(permissions = backgroundPermissions()) - .allPermissionsGranted - LocationRow( - location = viewState.location, - hasPermissions = hasPermissions, - onClick = { - viewState.location - ?.let { location -> - val options: MutableList Unit>> = ArrayList() - options.add(Pair.create(R.string.open_map) { location.open(activity) }) - if (!isNullOrEmpty(location.phone)) { - options.add(Pair.create(R.string.action_call) { call() }) - } - if (!isNullOrEmpty(location.url)) { - options.add(Pair.create(R.string.visit_website) { openWebsite() }) - } - options.add(Pair.create(R.string.choose_new_location) { chooseLocation() }) - options.add(Pair.create(R.string.delete) { - viewModel.setLocation( - null - ) - }) - val items = options.map { requireContext().getString(it.first!!) } - dialogBuilder - .newDialog(location.displayName) - .setItems(items) { _, which: Int -> - options[which].second!!() - } - .show() - } - ?: chooseLocation() - }, - openGeofenceOptions = { - if (hasPermissions) { - showGeofenceOptions() - } else { - newLocationPermissionDialog( - this@LocationControlSet, - REQUEST_LOCATION_PERMISSIONS - ) - .show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION) + @Composable + override fun Content() { + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + val hasPermissions = + rememberMultiplePermissionsState(permissions = backgroundPermissions()) + .allPermissionsGranted + LocationRow( + location = viewState.location, + hasPermissions = hasPermissions, + onClick = { + viewState.location + ?.let { location -> + val options: MutableList Unit>> = ArrayList() + options.add(Pair.create(R.string.open_map) { location.open(activity) }) + if (!isNullOrEmpty(location.phone)) { + options.add(Pair.create(R.string.action_call) { call() }) + } + if (!isNullOrEmpty(location.url)) { + options.add(Pair.create(R.string.visit_website) { openWebsite() }) } + options.add(Pair.create(R.string.choose_new_location) { chooseLocation() }) + options.add(Pair.create(R.string.delete) { + viewModel.setLocation( + null + ) + }) + val items = options.map { requireContext().getString(it.first!!) } + dialogBuilder + .newDialog(location.displayName) + .setItems(items) { _, which: Int -> + options[which].second!!() + } + .show() } - ) + ?: chooseLocation() + }, + openGeofenceOptions = { + if (hasPermissions) { + showGeofenceOptions() + } else { + newLocationPermissionDialog( + this@LocationControlSet, + REQUEST_LOCATION_PERMISSIONS + ) + .show(parentFragmentManager, FRAG_TAG_REQUEST_LOCATION) + } } - } + ) + } private fun openWebsite() { viewModel.viewState.value.location?.let { context?.openUri(it.url) } diff --git a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt index b295933fa..db68015e2 100644 --- a/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt +++ b/app/src/main/java/org/tasks/ui/SubtaskControlSet.kt @@ -1,12 +1,10 @@ package org.tasks.ui import android.app.Activity -import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.fragment.app.activityViewModels -import androidx.lifecycle.ViewModelProvider +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.lifecycleScope import com.todoroo.astrid.activity.MainActivityViewModel @@ -39,55 +37,51 @@ class SubtaskControlSet : TaskEditControlFragment() { @Inject lateinit var preferences: Preferences @Inject lateinit var firebase: Firebase - private lateinit var listViewModel: TaskListViewModel private val mainViewModel: MainActivityViewModel by activityViewModels() - override fun createView(savedInstanceState: Bundle?) { - viewModel.viewState.value.task.takeIf { it.id > 0 }?.let { - listViewModel.setFilter(SubtaskFilter(it.id)) + @Composable + override fun Content() { + val listViewModel: TaskListViewModel = hiltViewModel() + val viewState = viewModel.viewState.collectAsStateWithLifecycle().value + LaunchedEffect(viewState.task) { + if (viewState.task.id > 0) { + listViewModel.setFilter(SubtaskFilter(viewState.task.id)) + } } - } - - override fun bind(parent: ViewGroup?): View = - (parent as ComposeView).apply { - listViewModel = ViewModelProvider(requireParentFragment())[TaskListViewModel::class.java] - setContent { - val viewState = viewModel.viewState.collectAsStateWithLifecycle().value - val originalState = viewModel.originalState.collectAsStateWithLifecycle().value - SubtaskRow( - originalFilter = originalState.list, - filter = viewState.list, - hasParent = viewState.hasParent, - existingSubtasks = if (viewModel.viewState.collectAsStateWithLifecycle().value.isNew) { - TasksResults.Results(SectionedDataSource()) - } else { - listViewModel.state.collectAsStateWithLifecycle().value.tasks - }, - newSubtasks = viewState.newSubtasks, - openSubtask = this@SubtaskControlSet::openSubtask, - completeExistingSubtask = this@SubtaskControlSet::complete, - toggleSubtask = this@SubtaskControlSet::toggleSubtask, - addSubtask = { - lifecycleScope.launch { - viewModel.setSubtasks( - viewState.newSubtasks.plus(taskCreator.createWithValues("")) - ) - } - }, - completeNewSubtask = { - viewModel.setSubtasks( - viewState.newSubtasks.toMutableList().apply { - val modified = it.copy( - completionDate = if (it.isCompleted) 0 else currentTimeMillis() - ) - set(indexOf(it), modified) - } + val originalState = viewModel.originalState.collectAsStateWithLifecycle().value + SubtaskRow( + originalFilter = originalState.list, + filter = viewState.list, + hasParent = viewState.hasParent, + existingSubtasks = if (viewModel.viewState.collectAsStateWithLifecycle().value.isNew) { + TasksResults.Results(SectionedDataSource()) + } else { + listViewModel.state.collectAsStateWithLifecycle().value.tasks + }, + newSubtasks = viewState.newSubtasks, + openSubtask = this@SubtaskControlSet::openSubtask, + completeExistingSubtask = this@SubtaskControlSet::complete, + toggleSubtask = this@SubtaskControlSet::toggleSubtask, + addSubtask = { + lifecycleScope.launch { + viewModel.setSubtasks( + viewState.newSubtasks.plus(taskCreator.createWithValues("")) + ) + } + }, + completeNewSubtask = { + viewModel.setSubtasks( + viewState.newSubtasks.toMutableList().apply { + val modified = it.copy( + completionDate = if (it.isCompleted) 0 else currentTimeMillis() ) - }, - deleteSubtask = { viewModel.setSubtasks(viewState.newSubtasks - it) }, + set(indexOf(it), modified) + } ) - } - } + }, + deleteSubtask = { viewModel.setSubtasks(viewState.newSubtasks - it) }, + ) + } private fun openSubtask(task: Task) = lifecycleScope.launch { mainViewModel.setTask(task) diff --git a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt index 0705eb6ff..779879532 100644 --- a/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt +++ b/app/src/main/java/org/tasks/ui/TaskEditControlFragment.kt @@ -2,11 +2,11 @@ package org.tasks.ui import android.os.Bundle import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.compose.ui.platform.ComposeView +import androidx.compose.runtime.Composable import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.compose.content +import androidx.hilt.navigation.compose.hiltViewModel abstract class TaskEditControlFragment : Fragment() { lateinit var viewModel: TaskEditViewModel @@ -15,15 +15,11 @@ abstract class TaskEditControlFragment : Fragment() { inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { - val composeView = ComposeView(requireActivity()) - viewModel = ViewModelProvider(requireParentFragment())[TaskEditViewModel::class.java] - bind(composeView) - createView(savedInstanceState) - return composeView + ) = content { + viewModel = hiltViewModel(viewModelStoreOwner = requireParentFragment()) + Content() } - abstract fun bind(parent: ViewGroup?): View - - protected open fun createView(savedInstanceState: Bundle?) {} + @Composable + abstract fun Content() } \ No newline at end of file diff --git a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt index c0ee652a1..6d3729b80 100644 --- a/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt +++ b/app/src/main/java/org/tasks/ui/TaskEditViewModel.kt @@ -591,7 +591,7 @@ class TaskEditViewModel @Inject constructor( } init { - viewModelScope.launch { + runBlocking { val attachments = async { taskAttachmentDao .getAttachments(task.id) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6731fc9d7..1e1960465 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -78,6 +78,7 @@ androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscree androidx-datastore = { module = "androidx.datastore:datastore-preferences", version = "1.1.2" } androidx-fragment-compose = { module = "androidx.fragment:fragment-compose", version = "1.8.6" } androidx-hilt-compiler = { module = "androidx.hilt:hilt-compiler", version.ref = "hilt" } +androidx-hilt-navigation = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hilt" } androidx-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hilt" } androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }