Refactoring TaskEditControlFragments

pull/3363/head
Alex Baker 9 months ago
parent 2d8df0bb67
commit 840049b206

@ -177,6 +177,7 @@ dependencies {
implementation(libs.dagger.hilt) implementation(libs.dagger.hilt)
ksp(libs.dagger.hilt.compiler) ksp(libs.dagger.hilt.compiler)
ksp(libs.androidx.hilt.compiler) ksp(libs.androidx.hilt.compiler)
implementation(libs.androidx.hilt.navigation)
implementation(libs.androidx.hilt.work) implementation(libs.androidx.hilt.work)
implementation(libs.androidx.datastore) implementation(libs.androidx.datastore)

@ -8,10 +8,9 @@ package com.todoroo.astrid.files
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import androidx.compose.runtime.Composable
import android.view.View import androidx.compose.runtime.LaunchedEffect
import android.view.ViewGroup import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -33,43 +32,38 @@ import javax.inject.Inject
class FilesControlSet : TaskEditControlFragment() { class FilesControlSet : TaskEditControlFragment() {
@Inject lateinit var taskAttachmentDao: TaskAttachmentDao @Inject lateinit var taskAttachmentDao: TaskAttachmentDao
@Inject lateinit var preferences: Preferences @Inject lateinit var preferences: Preferences
override fun createView(savedInstanceState: Bundle?) { @Composable
val task = viewModel.viewState.value.task override fun Content() {
if (savedInstanceState == null) { val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
if (task.hasTransitory(TaskAttachment.KEY)) { LaunchedEffect(Unit) {
for (uri in (task.getTransitory<ArrayList<Uri>>(TaskAttachment.KEY))!!) { if (viewState.task.hasTransitory(TaskAttachment.KEY)) {
for (uri in (viewState.task.getTransitory<ArrayList<Uri>>(TaskAttachment.KEY))!!) {
newAttachment(uri) newAttachment(uri)
} }
} }
} }
} val context = LocalContext.current
AttachmentRow(
override fun bind(parent: ViewGroup?): View = attachments = viewState.attachments,
(parent as ComposeView).apply { openAttachment = {
setContent { Timber.d("Clicked open $it")
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value FileHelper.startActionView(
AttachmentRow( context,
attachments = viewState.attachments, if (Strings.isNullOrEmpty(it.uri)) null else Uri.parse(it.uri)
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)
},
) )
} },
} 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?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) { if (requestCode == AddAttachmentDialog.REQUEST_CAMERA || requestCode == AddAttachmentDialog.REQUEST_AUDIO) {

@ -7,12 +7,9 @@ package com.todoroo.astrid.repeats
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.Intent import android.content.Intent
import android.os.Bundle import androidx.compose.runtime.Composable
import android.view.View import androidx.compose.runtime.LaunchedEffect
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import net.fortuna.ical4j.model.Recur import net.fortuna.ical4j.model.Recur
import net.fortuna.ical4j.model.WeekDay import net.fortuna.ical4j.model.WeekDay
@ -67,36 +64,30 @@ class RepeatControlSet : TaskEditControlFragment() {
} }
} }
override fun createView(savedInstanceState: Bundle?) { @Composable
lifecycleScope.launchWhenResumed { override fun Content() {
viewModel.dueDate.collect { val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
onDueDateChanged() val dueDate = viewModel.dueDate.collectAsStateWithLifecycle().value
} LaunchedEffect(dueDate) {
onDueDateChanged()
} }
} RepeatRow(
recurrence = viewState.task.recurrence?.let { repeatRuleToString.toString(it) },
override fun bind(parent: ViewGroup?): View = repeatFrom = viewState.task.repeatFrom,
(parent as ComposeView).apply { onClick = {
setContent { val accountType = viewState.list.account.accountType
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value BasicRecurrenceDialog.newBasicRecurrenceDialog(
RepeatRow( target = this@RepeatControlSet,
recurrence = viewState.task.recurrence?.let { repeatRuleToString.toString(it) }, rc = REQUEST_RECURRENCE,
repeatFrom = viewState.task.repeatFrom, rrule = viewState.task.recurrence,
onClick = { dueDate = dueDate,
val accountType = viewState.list.account.accountType accountType = 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) }
) )
} .show(parentFragmentManager, FRAG_TAG_BASIC_RECURRENCE)
} },
onRepeatFromChanged = { viewModel.setRepeatFrom(it) }
)
}
companion object { companion object {
val TAG = R.string.TEA_ctrl_repeat_pref val TAG = R.string.TEA_ctrl_repeat_pref

@ -2,9 +2,7 @@ package com.todoroo.astrid.tags
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.view.View import androidx.compose.runtime.Composable
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -22,25 +20,23 @@ import javax.inject.Inject
class TagsControlSet : TaskEditControlFragment() { class TagsControlSet : TaskEditControlFragment() {
@Inject lateinit var chipProvider: ChipProvider @Inject lateinit var chipProvider: ChipProvider
private fun onRowClick() { @Composable
} override fun Content() {
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
override fun bind(parent: ViewGroup?): View = TagsRow(
(parent as ComposeView).apply { tags = viewState.tags,
setContent { colorProvider = { chipProvider.getColor(it) },
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value onClick = {
TagsRow( val intent = Intent(context, TagPickerActivity::class.java)
tags = viewState.tags, intent.putParcelableArrayListExtra(
colorProvider = { chipProvider.getColor(it) }, TagPickerActivity.EXTRA_SELECTED,
onClick = { ArrayList(viewState.tags)
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)) },
) )
} startActivityForResult(intent, REQUEST_TAG_PICKER_ACTIVITY)
} },
onClear = { viewModel.setTags(viewState.tags.minus(it)) },
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) { if (requestCode == REQUEST_TAG_PICKER_ACTIVITY) {

@ -6,12 +6,11 @@
package com.todoroo.astrid.timers package com.todoroo.astrid.timers
import android.app.Activity import android.app.Activity
import android.os.Bundle
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AlertDialog 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.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.ui.TimeDurationControlSet import com.todoroo.astrid.ui.TimeDurationControlSet
@ -45,14 +44,6 @@ class TimerControlSet : TaskEditControlFragment() {
private var dialog: AlertDialog? = null private var dialog: AlertDialog? = null
private lateinit var dialogView: View 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() { private fun onRowClick() {
if (dialog == null) { if (dialog == null) {
dialog = buildDialog() dialog = buildDialog()
@ -86,18 +77,23 @@ class TimerControlSet : TaskEditControlFragment() {
} }
} }
override fun bind(parent: ViewGroup?): View = @Composable
(parent as ComposeView).apply { override fun Content() {
setContent { LaunchedEffect(Unit) {
TimerRow( dialogView = activity.layoutInflater.inflate(R.layout.control_set_timers_dialog, null)
started = viewModel.timerStarted.collectAsStateWithLifecycle().value, estimated = TimeDurationControlSet(activity, dialogView, R.id.estimatedDuration, theme)
estimated = viewModel.estimatedSeconds.collectAsStateWithLifecycle().value, elapsed = TimeDurationControlSet(activity, dialogView, R.id.elapsedDuration, theme)
elapsed = viewModel.elapsedSeconds.collectAsStateWithLifecycle().value, estimated.setTimeDuration(viewModel.estimatedSeconds.value)
timerClicked = this@TimerControlSet::timerClicked, elapsed.setTimeDuration(viewModel.elapsedSeconds.value)
onClick = this@TimerControlSet::onRowClick,
)
}
} }
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 private fun timerActive() = viewModel.timerStarted.value > 0

@ -5,15 +5,14 @@ import android.app.Activity
import android.app.Activity.RESULT_OK import android.app.Activity.RESULT_OK
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts 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.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.PermissionStatus import com.google.accompanist.permissions.PermissionStatus
@ -39,94 +38,90 @@ class ReminderControlSet : TaskEditControlFragment() {
@Inject lateinit var activity: Activity @Inject lateinit var activity: Activity
@Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var dialogBuilder: DialogBuilder
private val ringMode = mutableStateOf(0) private val ringMode = mutableIntStateOf(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 fun setRingMode(ringMode: Int) { private fun setRingMode(ringMode: Int) {
viewModel.ringNonstop = ringMode == 2 viewModel.ringNonstop = ringMode == 2
viewModel.ringFiveTimes = ringMode == 1 viewModel.ringFiveTimes = ringMode == 1
this.ringMode.value = ringMode this.ringMode.intValue = ringMode
} }
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
override fun bind(parent: ViewGroup?): View = @Composable
(parent as ComposeView).apply { override fun Content() {
setContent { LaunchedEffect(Unit) {
val ringMode by remember { this@ReminderControlSet.ringMode } when {
val hasReminderPermissions by rememberReminderPermissionState() viewModel.ringNonstop -> setRingMode(2)
val notificationPermissions = if (AndroidUtilities.atLeastTiramisu()) { viewModel.ringFiveTimes -> setRingMode(1)
rememberPermissionState( else -> setRingMode(0)
Manifest.permission.POST_NOTIFICATIONS, }
onPermissionResult = { success -> }
if (success) { val ringMode by remember { this@ReminderControlSet.ringMode }
NotificationSchedulerIntentService.enqueueWork(context) 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 { } else {
null context.openReminderSettings()
} }
val pickDateAndTime = },
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> ringMode = ringMode,
if (result.resultCode != RESULT_OK) return@rememberLauncherForActivityResult addAlarm = viewModel::addAlarm,
val data = result.data ?: return@rememberLauncherForActivityResult openRingType = {
val timestamp = val modes = resources.getStringArray(R.array.reminder_ring_modes)
data.getLongExtra(MyTimePickerDialog.EXTRA_TIMESTAMP, 0L) val selectedIndex = when {
val replace: Alarm? = data.getParcelableExtra(EXTRA_REPLACE) viewModel.ringNonstop -> 2
replace?.let { viewModel.removeAlarm(it) } viewModel.ringFiveTimes -> 1
viewModel.addAlarm(Alarm(time = timestamp, type = TYPE_DATE_TIME)) else -> 0
} }
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value dialogBuilder
AlarmRow( .newDialog()
alarms = viewState.alarms, .setSingleChoiceItems(modes, selectedIndex) { dialog: DialogInterface, which: Int ->
hasNotificationPermissions = hasReminderPermissions && setRingMode(which)
(notificationPermissions == null || notificationPermissions.status == PermissionStatus.Granted), dialog.dismiss()
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)
)
} }
.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 { companion object {
val TAG = R.string.TEA_ctrl_reminders_pref val TAG = R.string.TEA_ctrl_reminders_pref

@ -3,12 +3,10 @@ package com.todoroo.astrid.ui
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import androidx.compose.runtime.Composable
import android.view.ViewGroup import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.tasks.R import org.tasks.R
@ -34,60 +32,55 @@ class StartDateControlSet : TaskEditControlFragment() {
private val vm: StartDateViewModel by viewModels() private val vm: StartDateViewModel by viewModels()
override fun createView(savedInstanceState: Bundle?) { @Composable
if (savedInstanceState == null) { override fun Content() {
LaunchedEffect(Unit) {
vm.init( vm.init(
dueDate = viewModel.dueDate.value, dueDate = viewModel.dueDate.value,
startDate = viewModel.startDate.value, startDate = viewModel.startDate.value,
isNew = viewModel.viewState.value.isNew isNew = viewModel.viewState.value.isNew
) )
} }
lifecycleScope.launchWhenResumed { val dueDate = viewModel.dueDate.collectAsStateWithLifecycle().value
viewModel.dueDate.collect { val selectedDay = vm.selectedDay.collectAsStateWithLifecycle().value
applySelected() 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?) = LaunchedEffect(dueDate) {
(parent as ComposeView).apply { applySelected()
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)
}
}
)
}
} }
}
@Deprecated("Deprecated in Java") @Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {

@ -4,11 +4,9 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.provider.CalendarContract import android.provider.CalendarContract
import android.view.View
import android.view.ViewGroup
import android.widget.Toast.LENGTH_SHORT import android.widget.Toast.LENGTH_SHORT
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.todoroo.astrid.activity.TaskEditFragment import com.todoroo.astrid.activity.TaskEditFragment
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -42,38 +40,36 @@ class CalendarControlSet : TaskEditControlFragment() {
} }
} }
override fun bind(parent: ViewGroup?): View = @Composable
(parent as ComposeView).apply { override fun Content() {
setContent { val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value CalendarRow(
CalendarRow( eventUri = viewModel.eventUri.collectAsStateWithLifecycle().value,
eventUri = viewModel.eventUri.collectAsStateWithLifecycle().value, selectedCalendar = remember(viewState.calendar) {
selectedCalendar = remember (viewState.calendar) { calendarProvider.getCalendar(viewState.calendar)?.name
calendarProvider.getCalendar(viewState.calendar)?.name },
}, onClick = {
onClick = { if (viewModel.eventUri.value.isNullOrBlank()) {
if (viewModel.eventUri.value.isNullOrBlank()) { CalendarPicker
CalendarPicker .newCalendarPicker(
.newCalendarPicker( requireParentFragment(),
requireParentFragment(), TaskEditFragment.REQUEST_CODE_PICK_CALENDAR,
TaskEditFragment.REQUEST_CODE_PICK_CALENDAR, viewState.calendar,
viewState.calendar, )
) .show(
.show( requireParentFragment().parentFragmentManager,
requireParentFragment().parentFragmentManager, TaskEditFragment.FRAG_TAG_CALENDAR_PICKER
TaskEditFragment.FRAG_TAG_CALENDAR_PICKER )
) } else {
} else { openCalendarEvent()
openCalendarEvent() }
} },
}, clear = {
clear = { viewModel.setCalendar(null)
viewModel.setCalendar(null) viewModel.eventUri.value = null
viewModel.eventUri.value = null
}
)
} }
} )
}
private fun openCalendarEvent() { private fun openCalendarEvent() {
val cr = activity.contentResolver val cr = activity.contentResolver

@ -4,9 +4,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Parcelable import android.os.Parcelable
import android.view.View import androidx.compose.runtime.Composable
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.core.util.Pair import androidx.core.util.Pair
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -53,57 +51,55 @@ class LocationControlSet : TaskEditControlFragment() {
} }
@OptIn(ExperimentalPermissionsApi::class) @OptIn(ExperimentalPermissionsApi::class)
override fun bind(parent: ViewGroup?): View = @Composable
(parent as ComposeView).apply { override fun Content() {
setContent { val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value val hasPermissions =
val hasPermissions = rememberMultiplePermissionsState(permissions = backgroundPermissions())
rememberMultiplePermissionsState(permissions = backgroundPermissions()) .allPermissionsGranted
.allPermissionsGranted LocationRow(
LocationRow( location = viewState.location,
location = viewState.location, hasPermissions = hasPermissions,
hasPermissions = hasPermissions, onClick = {
onClick = { viewState.location
viewState.location ?.let { location ->
?.let { location -> val options: MutableList<Pair<Int, () -> Unit>> = ArrayList()
val options: MutableList<Pair<Int, () -> Unit>> = ArrayList() options.add(Pair.create(R.string.open_map) { location.open(activity) })
options.add(Pair.create(R.string.open_map) { location.open(activity) }) if (!isNullOrEmpty(location.phone)) {
if (!isNullOrEmpty(location.phone)) { options.add(Pair.create(R.string.action_call) { call() })
options.add(Pair.create(R.string.action_call) { call() }) }
} if (!isNullOrEmpty(location.url)) {
if (!isNullOrEmpty(location.url)) { options.add(Pair.create(R.string.visit_website) { openWebsite() })
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)
} }
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() { private fun openWebsite() {
viewModel.viewState.value.location?.let { context?.openUri(it.url) } viewModel.viewState.value.location?.let { context?.openUri(it.url) }

@ -1,12 +1,10 @@
package org.tasks.ui package org.tasks.ui
import android.app.Activity import android.app.Activity
import android.os.Bundle import androidx.compose.runtime.Composable
import android.view.View import androidx.compose.runtime.LaunchedEffect
import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.ViewModelProvider import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.todoroo.astrid.activity.MainActivityViewModel import com.todoroo.astrid.activity.MainActivityViewModel
@ -39,55 +37,51 @@ class SubtaskControlSet : TaskEditControlFragment() {
@Inject lateinit var preferences: Preferences @Inject lateinit var preferences: Preferences
@Inject lateinit var firebase: Firebase @Inject lateinit var firebase: Firebase
private lateinit var listViewModel: TaskListViewModel
private val mainViewModel: MainActivityViewModel by activityViewModels() private val mainViewModel: MainActivityViewModel by activityViewModels()
override fun createView(savedInstanceState: Bundle?) { @Composable
viewModel.viewState.value.task.takeIf { it.id > 0 }?.let { override fun Content() {
listViewModel.setFilter(SubtaskFilter(it.id)) val listViewModel: TaskListViewModel = hiltViewModel()
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value
LaunchedEffect(viewState.task) {
if (viewState.task.id > 0) {
listViewModel.setFilter(SubtaskFilter(viewState.task.id))
}
} }
} val originalState = viewModel.originalState.collectAsStateWithLifecycle().value
SubtaskRow(
override fun bind(parent: ViewGroup?): View = originalFilter = originalState.list,
(parent as ComposeView).apply { filter = viewState.list,
listViewModel = ViewModelProvider(requireParentFragment())[TaskListViewModel::class.java] hasParent = viewState.hasParent,
setContent { existingSubtasks = if (viewModel.viewState.collectAsStateWithLifecycle().value.isNew) {
val viewState = viewModel.viewState.collectAsStateWithLifecycle().value TasksResults.Results(SectionedDataSource())
val originalState = viewModel.originalState.collectAsStateWithLifecycle().value } else {
SubtaskRow( listViewModel.state.collectAsStateWithLifecycle().value.tasks
originalFilter = originalState.list, },
filter = viewState.list, newSubtasks = viewState.newSubtasks,
hasParent = viewState.hasParent, openSubtask = this@SubtaskControlSet::openSubtask,
existingSubtasks = if (viewModel.viewState.collectAsStateWithLifecycle().value.isNew) { completeExistingSubtask = this@SubtaskControlSet::complete,
TasksResults.Results(SectionedDataSource()) toggleSubtask = this@SubtaskControlSet::toggleSubtask,
} else { addSubtask = {
listViewModel.state.collectAsStateWithLifecycle().value.tasks lifecycleScope.launch {
}, viewModel.setSubtasks(
newSubtasks = viewState.newSubtasks, viewState.newSubtasks.plus(taskCreator.createWithValues(""))
openSubtask = this@SubtaskControlSet::openSubtask, )
completeExistingSubtask = this@SubtaskControlSet::complete, }
toggleSubtask = this@SubtaskControlSet::toggleSubtask, },
addSubtask = { completeNewSubtask = {
lifecycleScope.launch { viewModel.setSubtasks(
viewModel.setSubtasks( viewState.newSubtasks.toMutableList().apply {
viewState.newSubtasks.plus(taskCreator.createWithValues("")) val modified = it.copy(
) completionDate = if (it.isCompleted) 0 else currentTimeMillis()
}
},
completeNewSubtask = {
viewModel.setSubtasks(
viewState.newSubtasks.toMutableList().apply {
val modified = it.copy(
completionDate = if (it.isCompleted) 0 else currentTimeMillis()
)
set(indexOf(it), modified)
}
) )
}, set(indexOf(it), modified)
deleteSubtask = { viewModel.setSubtasks(viewState.newSubtasks - it) }, }
) )
} },
} deleteSubtask = { viewModel.setSubtasks(viewState.newSubtasks - it) },
)
}
private fun openSubtask(task: Task) = lifecycleScope.launch { private fun openSubtask(task: Task) = lifecycleScope.launch {
mainViewModel.setTask(task) mainViewModel.setTask(task)

@ -2,11 +2,11 @@ package org.tasks.ui
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.compose.ui.platform.ComposeView import androidx.compose.runtime.Composable
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.fragment.compose.content
import androidx.hilt.navigation.compose.hiltViewModel
abstract class TaskEditControlFragment : Fragment() { abstract class TaskEditControlFragment : Fragment() {
lateinit var viewModel: TaskEditViewModel lateinit var viewModel: TaskEditViewModel
@ -15,15 +15,11 @@ abstract class TaskEditControlFragment : Fragment() {
inflater: LayoutInflater, inflater: LayoutInflater,
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View? { ) = content {
val composeView = ComposeView(requireActivity()) viewModel = hiltViewModel<TaskEditViewModel>(viewModelStoreOwner = requireParentFragment())
viewModel = ViewModelProvider(requireParentFragment())[TaskEditViewModel::class.java] Content()
bind(composeView)
createView(savedInstanceState)
return composeView
} }
abstract fun bind(parent: ViewGroup?): View @Composable
abstract fun Content()
protected open fun createView(savedInstanceState: Bundle?) {}
} }

@ -591,7 +591,7 @@ class TaskEditViewModel @Inject constructor(
} }
init { init {
viewModelScope.launch { runBlocking {
val attachments = async { val attachments = async {
taskAttachmentDao taskAttachmentDao
.getAttachments(task.id) .getAttachments(task.id)

@ -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-datastore = { module = "androidx.datastore:datastore-preferences", version = "1.1.2" }
androidx-fragment-compose = { module = "androidx.fragment:fragment-compose", version = "1.8.6" } 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-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-hilt-work = { module = "androidx.hilt:hilt-work", version.ref = "hilt" }
androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" } androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junit" }
androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" } androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime", version.ref = "lifecycle" }

Loading…
Cancel
Save