Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,21 @@ import com.wire.android.mapper.UIAssetMapper
import com.wire.android.ui.common.monthYearHeader
import com.wire.android.ui.home.conversations.model.messagetypes.asset.UIAssetMessage
import com.wire.android.util.dispatchers.DispatcherProvider
import com.wire.android.util.time.TimeZoneProvider
import com.wire.android.util.time.CurrentTimeZoneProvider
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.feature.asset.ObservePaginatedAssetImageMessages
import dev.zacsweers.metro.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.datetime.toLocalDateTime
import dev.zacsweers.metro.Inject
import kotlin.math.max

class ObserveImageAssetMessagesFromConversationUseCase @Inject constructor(
private val getAssetMessages: ObservePaginatedAssetImageMessages,
private val assetMapper: UIAssetMapper,
private val dispatchers: DispatcherProvider,
private val timeZoneProvider: TimeZoneProvider
private val timeZoneProvider: CurrentTimeZoneProvider
) {

/**
Expand All @@ -63,7 +63,7 @@ class ObserveImageAssetMessagesFromConversationUseCase @Inject constructor(
startingOffset = max(0, initialOffset - PREFETCH_DISTANCE).toLong(),
pagingConfig = pagingConfig
).map { pagingData ->
val currentTime = timeZoneProvider.currentSystemDefault()
val currentTime = timeZoneProvider()
pagingData.map { assetMessage ->
UIImageAssetPagingItem.Asset(assetMapper.toUIAsset(assetMessage))
}.insertSeparators { before: UIImageAssetPagingItem.Asset?, after: UIImageAssetPagingItem.Asset? ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import androidx.paging.testing.asSnapshot
import com.wire.android.config.CoroutineTestExtension
import com.wire.android.config.TestDispatcherProvider
import com.wire.android.mapper.UIAssetMapper
import com.wire.android.util.time.TimeZoneProvider
import com.wire.android.util.time.CurrentTimeZoneProvider
import com.wire.kalium.logic.data.asset.AssetMessage
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.feature.asset.ObservePaginatedAssetImageMessages
Expand Down Expand Up @@ -77,7 +77,7 @@ class ObserveImageAssetMessagesFromConversationUseCaseTest {
lateinit var getAssetMessages: ObservePaginatedAssetImageMessages

@MockK
lateinit var timeZoneProvider: TimeZoneProvider
lateinit var timeZoneProvider: CurrentTimeZoneProvider

private val useCase: ObserveImageAssetMessagesFromConversationUseCase by lazy {
ObserveImageAssetMessagesFromConversationUseCase(
Expand All @@ -90,7 +90,7 @@ class ObserveImageAssetMessagesFromConversationUseCaseTest {

init {
MockKAnnotations.init(this, relaxUnitFun = true)
every { timeZoneProvider.currentSystemDefault() } returns TimeZone.of("UTC")
every { timeZoneProvider.invoke() } returns TimeZone.of("UTC")
}

suspend fun sendPagingData(pagingItems: List<AssetMessage>) = apply {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@
*/
package com.wire.android.util.time

import kotlinx.datetime.TimeZone
import dev.zacsweers.metro.Inject
import kotlinx.datetime.TimeZone

class CurrentTimeZoneProvider(val currentTimeZone: () -> TimeZone) {
@Inject constructor() : this(TimeZone::currentSystemDefault)

operator fun invoke(): TimeZone = currentTimeZone()

Check warning on line 26 in core/ui-common/src/main/kotlin/com/wire/android/util/time/CurrentTimeZoneProvider.kt

View check run for this annotation

Codecov / codecov/patch

core/ui-common/src/main/kotlin/com/wire/android/util/time/CurrentTimeZoneProvider.kt#L26

Added line #L26 was not covered by tests

class TimeZoneProvider @Inject constructor() {
fun currentSystemDefault(): TimeZone = TimeZone.currentSystemDefault()
companion object {
val Preview: CurrentTimeZoneProvider = CurrentTimeZoneProvider()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ import com.wire.android.ui.theme.wireTypography
import com.wire.android.util.CurrentTimeProvider
import com.wire.android.util.DateAndTimeParsers
import com.wire.android.util.EMPTY
import com.wire.android.util.time.CurrentTimeZoneProvider
import com.wire.kalium.logic.data.id.MeetingId
import com.wire.kalium.logic.data.user.ConnectionState
import kotlinx.collections.immutable.ImmutableSet
Expand Down Expand Up @@ -570,7 +571,7 @@ fun PreviewNewMeetingScreen_MeetNow() = WireTheme {
NewMeetingContent(
titleState = rememberTextFieldState("Meeting with 9 users"),
type = NewMeetingType.MeetNow,
state = NewMeetingState.initialState(CurrentTimeProvider.Preview).copy(
state = NewMeetingState.initialState(CurrentTimeProvider.Preview, CurrentTimeZoneProvider.Preview).copy(
confirmedContacts = buildContacts(names.size),
continueButtonEnabled = true,
initialLoading = NewMeetingState.InitialLoadingState.Loaded,
Expand All @@ -584,9 +585,9 @@ fun PreviewNewMeetingScreen_Schedule() = WireTheme {
NewMeetingContent(
titleState = rememberTextFieldState(),
type = NewMeetingType.Schedule,
state = NewMeetingState.initialState(CurrentTimeProvider.Preview).copy(
startTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()),
endTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()).plus(1.hours),
state = NewMeetingState.initialState(CurrentTimeProvider.Preview, CurrentTimeZoneProvider.Preview).copy(
startTime = getNextFullHour(CurrentTimeProvider.Preview.invoke(), CurrentTimeZoneProvider.Preview.invoke()),
endTime = getNextFullHour(CurrentTimeProvider.Preview.invoke(), CurrentTimeZoneProvider.Preview.invoke()).plus(1.hours),
repeatingInterval = MeetingItem.RepeatingInterval.Supported.first(),
initialLoading = NewMeetingState.InitialLoadingState.Loaded,
),
Expand All @@ -599,9 +600,9 @@ fun PreviewNewMeetingScreen_Edit() = WireTheme {
NewMeetingContent(
titleState = rememberTextFieldState(),
type = NewMeetingType.Edit(MeetingId("meetingId", "domain")),
state = NewMeetingState.initialState(CurrentTimeProvider.Preview).copy(
startTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()),
endTime = getNextFullHour(CurrentTimeProvider.Preview.invoke()).plus(1.hours),
state = NewMeetingState.initialState(CurrentTimeProvider.Preview, CurrentTimeZoneProvider.Preview).copy(
startTime = getNextFullHour(CurrentTimeProvider.Preview.invoke(), CurrentTimeZoneProvider.Preview.invoke()),
endTime = getNextFullHour(CurrentTimeProvider.Preview.invoke(), CurrentTimeZoneProvider.Preview.invoke()).plus(1.hours),
repeatingInterval = MeetingItem.RepeatingInterval.Supported.first(),
initialLoading = NewMeetingState.InitialLoadingState.Loaded,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.wire.android.ui.common.ActionsViewModel
import com.wire.android.ui.common.textfield.textAsFlow
import com.wire.android.util.CurrentTimeProvider
import com.wire.android.util.time.CurrentTimeZoneProvider
import com.wire.kalium.logic.data.id.ConversationId
import com.wire.kalium.logic.data.meeting.Meeting
import com.wire.kalium.logic.data.meeting.UpsertMeeting
Expand Down Expand Up @@ -69,6 +70,7 @@

interface NewMeetingViewModel : ActionsManager<NewMeetingViewActions> {
val currentTimeProvider: CurrentTimeProvider
val currentTimeZoneProvider: CurrentTimeZoneProvider
val type: NewMeetingType
val titleTextState: TextFieldState
val state: NewMeetingState
Expand All @@ -93,15 +95,17 @@
override val type: NewMeetingType
) : NewMeetingViewModel {
override val currentTimeProvider: CurrentTimeProvider = CurrentTimeProvider.Preview
override val currentTimeZoneProvider: CurrentTimeZoneProvider = CurrentTimeZoneProvider.Preview

Check warning on line 98 in features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt

View check run for this annotation

Codecov / codecov/patch

features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt#L98

Added line #L98 was not covered by tests
override val titleTextState: TextFieldState = TextFieldState()
override val state: NewMeetingState = initialState(currentTimeProvider)
override val state: NewMeetingState = initialState(currentTimeProvider, currentTimeZoneProvider)

Check warning on line 100 in features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt

View check run for this annotation

Codecov / codecov/patch

features/meetings/src/main/java/com/wire/android/feature/meetings/ui/create/NewMeetingViewModel.kt#L100

Added line #L100 was not covered by tests
}

@Suppress("TooManyFunctions")
@WireAssistedViewModelBinding(MeetingsManualViewModelFactoryGroup::class)
class NewMeetingViewModelImpl @AssistedInject constructor(
@Assisted val navArgs: NewMeetingNavArgs,
override val currentTimeProvider: CurrentTimeProvider,
override val currentTimeZoneProvider: CurrentTimeZoneProvider,
private val createNewMeeting: CreateNewMeetingUseCase,
private val updateMeeting: UpdateMeetingUseCase,
private val getNextUnfinishedMeetingOccurrence: GetNextUnfinishedMeetingOccurrenceUseCase,
Expand All @@ -116,7 +120,7 @@

override val type: NewMeetingType = navArgs.type
override val titleTextState: TextFieldState = TextFieldState()
override var state: NewMeetingState by mutableStateOf(initialState(currentTimeProvider))
override var state: NewMeetingState by mutableStateOf(initialState(currentTimeProvider, currentTimeZoneProvider))
private set

init {
Expand All @@ -138,6 +142,7 @@
state = state.copy(
startTime = meetingOccurrence.occurrenceStartTime,
endTime = meetingOccurrence.occurrenceEndTime,
tzid = meetingOccurrence.meeting.tzid,
repeatingInterval = meetingOccurrence.meeting.recurrence?.toRepeatingInterval(),
selectedContacts = otherContacts,
confirmedContacts = otherContacts,
Expand Down Expand Up @@ -180,20 +185,30 @@

override fun updateStartTime(startTime: Instant) {
val currentDuration = state.endTime - state.startTime
val latestEndTime = startTime.latestEndTimeOnSameDay()
val latestEndTime = startTime.latestEndTimeOnSameDay(currentTimeZoneProvider())
val updatedEndTime = minOf(startTime.plus(currentDuration), latestEndTime)
state = state.copy(
startTime = startTime,
// adjust end time based on the new start time but try to keep the same duration, unless it extends into the next day
endTime = minOf(startTime.plus(currentDuration), latestEndTime)
endTime = updatedEndTime,
tzid = tzidAfterTimeChange(startTime != state.startTime || updatedEndTime != state.endTime)
)
validateStartAndEndTime()
}

override fun updateEndTime(endTime: Instant) {
state = state.copy(endTime = endTime)
state = state.copy(
endTime = endTime,
tzid = tzidAfterTimeChange(endTime != state.endTime)
)
validateStartAndEndTime()
}

// While editing, keep the original meeting tzid unless the user changes a time value.
// Time picker changes are made in the user's local timezone, so edited times should carry the local tzid.
private fun tzidAfterTimeChange(timeChanged: Boolean): String =
if (type is NewMeetingType.Edit && !timeChanged) state.tzid else currentTimeZoneProvider().id

override fun updateRepeatingInterval(interval: MeetingItem.RepeatingInterval?) {
state = state.copy(repeatingInterval = interval)
}
Expand Down Expand Up @@ -256,6 +271,7 @@
title = titleTextState.text.trim().toString(),
startTime = state.startTime,
endTime = state.endTime,
tzid = state.tzid,
recurrence = state.repeatingInterval?.let { Meeting.Recurrence(it.frequency, it.interval.toLong(), null) },
otherParticipants = state.confirmedContacts.map { UserId(it.id, it.domain) }
)
Expand All @@ -282,6 +298,7 @@
title = titleTextState.text.trim().toString(),
startTime = state.startTime,
endTime = state.endTime,
tzid = state.tzid,
recurrence = state.repeatingInterval?.let { Meeting.Recurrence(it.frequency, it.interval.toLong(), null) },
otherParticipants = state.confirmedContacts.map { UserId(it.id, it.domain) }
)
Expand Down Expand Up @@ -321,7 +338,7 @@
}
}

internal fun getNextFullHour(now: Instant, timeZone: TimeZone = TimeZone.currentSystemDefault()): Instant {
internal fun getNextFullHour(now: Instant, timeZone: TimeZone): Instant {
val futureHour = now.plus(1, DateTimeUnit.HOUR, timeZone)
val localFuture = futureHour.toLocalDateTime(timeZone)
return LocalDateTime(
Expand All @@ -337,7 +354,7 @@

// Find the latest possible end time on the same day as the given Instant, in the given time zone.
// The latest possible end time is 23:59:00 on the same day in the given time zone.
private fun Instant.latestEndTimeOnSameDay(timeZone: TimeZone = TimeZone.currentSystemDefault()): Instant {
private fun Instant.latestEndTimeOnSameDay(timeZone: TimeZone): Instant {
val localStartTime = toLocalDateTime(timeZone)
return LocalDateTime(
year = localStartTime.year,
Expand All @@ -360,6 +377,7 @@
val startTimeError: TimeError? = null,
val endTime: Instant,
val endTimeError: TimeError? = null,
val tzid: String,
val repeatingInterval: MeetingItem.RepeatingInterval? = null,
val submitError: SubmitError? = null,
val isSubmitting: Boolean = false,
Expand All @@ -385,9 +403,13 @@
}

companion object {
fun initialState(currentTimeProvider: CurrentTimeProvider): NewMeetingState {
val startTime = getNextFullHour(currentTimeProvider())
return NewMeetingState(startTime = startTime, endTime = startTime.plus(1.hours))
fun initialState(currentTimeProvider: CurrentTimeProvider, currentTimeZoneProvider: CurrentTimeZoneProvider): NewMeetingState {
val startTime = getNextFullHour(currentTimeProvider(), currentTimeZoneProvider())
return NewMeetingState(
startTime = startTime,
endTime = startTime.plus(1.hours),
tzid = currentTimeZoneProvider().id,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class MeetingMapperTest {
title = TITLE,
startTime = startTime,
endTime = endTime,
tzid = "Europe/Berlin",
recurrence = Meeting.Recurrence(frequency = Meeting.Recurrence.Frequency.DAILY, interval = 1L, until = null),
),
occurrenceId = "$MEETING_ID-occurrence",
Expand Down
Loading
Loading