Use formatter for LLS duration

This commit is contained in:
ganfra 2026-03-24 10:15:25 +01:00
parent 6507a2e8cb
commit dee8ce27e7
12 changed files with 275 additions and 24 deletions

View file

@ -29,21 +29,18 @@ fun LocationConstraintsDialog(
onSubmitClick = onRequestPermissions,
onDismiss = onDismiss,
submitText = stringResource(CommonStrings.action_continue),
cancelText = stringResource(CommonStrings.action_cancel),
)
LocationConstraintsDialogState.PermissionDenied -> ConfirmationDialog(
content = stringResource(CommonStrings.error_missing_location_auth_android, appName),
onSubmitClick = onOpenAppSettings,
onDismiss = onDismiss,
submitText = stringResource(CommonStrings.action_continue),
cancelText = stringResource(CommonStrings.action_cancel),
)
LocationConstraintsDialogState.LocationServiceDisabled -> ConfirmationDialog(
content = stringResource(CommonStrings.error_location_service_disabled_android),
onSubmitClick = onOpenLocationSettings,
onDismiss = onDismiss,
submitText = stringResource(CommonStrings.action_continue),
cancelText = stringResource(CommonStrings.action_cancel),
)
}
}

View file

@ -8,14 +8,8 @@
package io.element.android.features.location.impl.share
import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
enum class LiveLocationDuration(
data class LiveLocationDuration(
val duration: Duration,
val label: String,
) {
FifteenMinutes(15.minutes, "15 minutes"),
OneHour(1.hours, "1 hour"),
EightHours(8.hours, "8 hours")
}
val formatted: String
)

View file

@ -34,6 +34,7 @@ import io.element.android.features.messages.api.MessageComposerContext
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.extensions.flatMap
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.dateformatter.api.DurationFormatter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.MatrixClient
@ -43,7 +44,12 @@ import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
private val LIVE_LOCATION_DURATIONS = listOf(15.minutes, 1.hours, 8.hours)
@AssistedInject
class ShareLocationPresenter(
@ -56,6 +62,7 @@ class ShareLocationPresenter(
private val buildMeta: BuildMeta,
private val featureFlagService: FeatureFlagService,
private val client: MatrixClient,
private val durationFormatter: DurationFormatter,
) : Presenter<ShareLocationState> {
@AssistedFactory
fun interface Factory {
@ -105,7 +112,10 @@ class ShareLocationPresenter(
ShareLocationEvent.ShowLiveLocationDurationPicker -> {
val constraintsResult = checkLocationConstraints(permissionsState, locationActions)
dialogState = if (constraintsResult is LocationConstraintsCheck.Success) {
ShareLocationState.Dialog.LiveLocationDuration
val durations = LIVE_LOCATION_DURATIONS.map {
LiveLocationDuration(duration = it, formatted = durationFormatter.format(it))
}
ShareLocationState.Dialog.LiveLocationDurations(durations.toImmutableList())
} else {
Constraints(constraintsResult.toDialogState())
}

View file

@ -10,6 +10,7 @@ package io.element.android.features.location.impl.share
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList
data class ShareLocationState(
val currentUser: MatrixUser,
@ -23,6 +24,6 @@ data class ShareLocationState(
sealed interface Dialog {
data object None : Dialog
data class Constraints(val state: LocationConstraintsDialogState) : Dialog
data object LiveLocationDuration : Dialog
data class LiveLocationDurations(val durations: ImmutableList<LiveLocationDuration>) : Dialog
}
}

View file

@ -12,6 +12,9 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.persistentListOf
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes
private const val APP_NAME = "ApplicationName"
@ -49,7 +52,13 @@ class ShareLocationStateProvider : PreviewParameterProvider<ShareLocationState>
hasLocationPermission = true,
),
aShareLocationState(
dialogState = ShareLocationState.Dialog.LiveLocationDuration,
dialogState = ShareLocationState.Dialog.LiveLocationDurations(
persistentListOf(
LiveLocationDuration(15.minutes, "15 minutes"),
LiveLocationDuration(1.hours, "1 hour"),
LiveLocationDuration(8.hours, "8 hours"),
)
),
trackUserPosition = true,
hasLocationPermission = true,
canShareLiveLocation = true,

View file

@ -59,6 +59,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
import org.maplibre.compose.camera.CameraMoveReason
import org.maplibre.compose.camera.CameraState
import org.maplibre.compose.camera.rememberCameraState
@ -83,7 +84,8 @@ fun ShareLocationView(
onOpenLocationSettings = { state.eventSink(ShareLocationEvent.OpenLocationSettings) },
onDismiss = { state.eventSink(ShareLocationEvent.DismissDialog) },
)
ShareLocationState.Dialog.LiveLocationDuration -> LiveLocationDurationDialog(
is ShareLocationState.Dialog.LiveLocationDurations -> LiveLocationDurationDialog(
durations = dialogState.durations,
onSelectDuration = { duration ->
state.eventSink(ShareLocationEvent.StartLiveLocationShare(duration))
context.toast("Not implemented yet!")
@ -252,6 +254,7 @@ private fun ShareLiveLocationItem(
@Composable
private fun LiveLocationDurationDialog(
durations: ImmutableList<LiveLocationDuration>,
onSelectDuration: (Duration) -> Unit,
onDismiss: () -> Unit,
) {
@ -259,14 +262,14 @@ private fun LiveLocationDurationDialog(
ListDialog(
title = "Choose how long to share your live location.",
submitText = stringResource(CommonStrings.action_continue),
onSubmit = { onSelectDuration(LiveLocationDuration.entries[selectedIndex].duration) },
onSubmit = { onSelectDuration(durations[selectedIndex].duration) },
onDismissRequest = onDismiss,
applyPaddingToContents = false,
verticalArrangement = Arrangement.Top
) {
itemsIndexed(LiveLocationDuration.entries) { index, duration ->
itemsIndexed(durations) { index, duration ->
RadioButtonListItem(
headline = duration.label,
headline = duration.formatted,
selected = index == selectedIndex,
onSelect = { selectedIndex = index },
compactLayout = true,

View file

@ -14,6 +14,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.location.impl.common.actions.FakeLocationActions
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.test.FakeMatrixClient
@ -46,6 +47,7 @@ class DefaultShareLocationEntryPointTest {
buildMeta = aBuildMeta(),
featureFlagService = FakeFeatureFlagService(),
client = FakeMatrixClient(),
durationFormatter = FakeDurationFormatter(),
)
},
analyticsService = FakeAnalyticsService(),

View file

@ -18,10 +18,10 @@ import io.element.android.features.location.impl.aPermissionsState
import io.element.android.features.location.impl.common.actions.FakeLocationActions
import io.element.android.features.location.impl.common.permissions.FakePermissionsPresenter
import io.element.android.features.location.impl.common.permissions.PermissionsEvents
import io.element.android.features.location.impl.common.permissions.PermissionsPresenter
import io.element.android.features.location.impl.common.permissions.PermissionsState
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
import io.element.android.features.messages.test.FakeMessageComposerContext
import io.element.android.libraries.dateformatter.test.FakeDurationFormatter
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.room.JoinedRoom
@ -54,13 +54,13 @@ class ShareLocationPresenterTest {
private val fakeFeatureFlagService = FakeFeatureFlagService()
private val fakeMatrixClient = FakeMatrixClient(sessionId = A_USER_ID)
private val durationFormatter = FakeDurationFormatter()
private fun createShareLocationPresenter(
joinedRoom: JoinedRoom = FakeJoinedRoom(),
locationActions: FakeLocationActions = fakeLocationActions,
): ShareLocationPresenter = ShareLocationPresenter(
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permissions: List<String>): PermissionsPresenter = fakePermissionsPresenter
},
permissionsPresenterFactory = { fakePermissionsPresenter },
room = joinedRoom,
timelineMode = Timeline.Mode.Live,
analyticsService = fakeAnalyticsService,
@ -69,6 +69,7 @@ class ShareLocationPresenterTest {
buildMeta = fakeBuildMeta,
featureFlagService = fakeFeatureFlagService,
client = fakeMatrixClient,
durationFormatter = durationFormatter,
)
@Test
@ -306,7 +307,7 @@ class ShareLocationPresenterTest {
initialState.eventSink(ShareLocationEvent.ShowLiveLocationDurationPicker)
val durationDialogState = awaitItem()
assertThat(durationDialogState.dialogState).isEqualTo(ShareLocationState.Dialog.LiveLocationDuration)
assertThat(durationDialogState.dialogState).isInstanceOf(ShareLocationState.Dialog.LiveLocationDurations::class.java)
cancelAndIgnoreRemainingEvents()
}
}