Benoit Marty
10 months ago
committed by
Benoit Marty
16 changed files with 290 additions and 17 deletions
@ -0,0 +1,24 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 New Vector Ltd |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet |
||||||
|
|
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem |
||||||
|
|
||||||
|
sealed interface ReadReceiptBottomSheetEvents { |
||||||
|
data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvents |
||||||
|
data object Dismiss : ReadReceiptBottomSheetEvents |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 New Vector Ltd |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet |
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.getValue |
||||||
|
import androidx.compose.runtime.mutableStateOf |
||||||
|
import androidx.compose.runtime.remember |
||||||
|
import androidx.compose.runtime.setValue |
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem |
||||||
|
import io.element.android.libraries.architecture.Presenter |
||||||
|
import javax.inject.Inject |
||||||
|
|
||||||
|
class ReadReceiptBottomSheetPresenter @Inject constructor( |
||||||
|
) : Presenter<ReadReceiptBottomSheetState> { |
||||||
|
|
||||||
|
@Composable |
||||||
|
override fun present(): ReadReceiptBottomSheetState { |
||||||
|
var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } |
||||||
|
|
||||||
|
fun handleEvent(event: ReadReceiptBottomSheetEvents) { |
||||||
|
@Suppress("LiftReturnOrAssignment") |
||||||
|
when (event) { |
||||||
|
is ReadReceiptBottomSheetEvents.EventSelected -> { |
||||||
|
selectedEvent = event.event |
||||||
|
} |
||||||
|
ReadReceiptBottomSheetEvents.Dismiss -> { |
||||||
|
selectedEvent = null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return ReadReceiptBottomSheetState( |
||||||
|
selectedEvent = selectedEvent, |
||||||
|
eventSink = { handleEvent(it) }, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 New Vector Ltd |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet |
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable |
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem |
||||||
|
|
||||||
|
@Immutable |
||||||
|
data class ReadReceiptBottomSheetState( |
||||||
|
val selectedEvent: TimelineItem.Event?, |
||||||
|
val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, |
||||||
|
) |
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 New Vector Ltd |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet |
||||||
|
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider |
||||||
|
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent |
||||||
|
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewStateProvider |
||||||
|
import io.element.android.features.messages.impl.timeline.model.TimelineItemReadReceipts |
||||||
|
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState |
||||||
|
import kotlinx.collections.immutable.toImmutableList |
||||||
|
|
||||||
|
class ReadReceiptBottomSheetStateProvider : PreviewParameterProvider<ReadReceiptBottomSheetState> { |
||||||
|
// Reuse the provider ReadReceiptViewStateProvider |
||||||
|
private val readReceiptViewStateProvider = ReadReceiptViewStateProvider() |
||||||
|
override val values: Sequence<ReadReceiptBottomSheetState> = readReceiptViewStateProvider.values |
||||||
|
.filter { it.sendState is LocalEventSendState.Sent } |
||||||
|
.map { readReceiptViewState -> |
||||||
|
ReadReceiptBottomSheetState( |
||||||
|
selectedEvent = aTimelineItemEvent( |
||||||
|
readReceiptState = TimelineItemReadReceipts.ReadReceipts( |
||||||
|
receipts = readReceiptViewState.receipts.map { readReceiptData -> |
||||||
|
readReceiptData |
||||||
|
.copy(avatarData = readReceiptData.avatarData.copy(id = "@${readReceiptData.avatarData.id}:localhost")) |
||||||
|
}.toImmutableList() |
||||||
|
) |
||||||
|
), |
||||||
|
eventSink = {}, |
||||||
|
) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,107 @@ |
|||||||
|
/* |
||||||
|
* Copyright (c) 2023 New Vector Ltd |
||||||
|
* |
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
* you may not use this file except in compliance with the License. |
||||||
|
* You may obtain a copy of the License at |
||||||
|
* |
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
* |
||||||
|
* Unless required by applicable law or agreed to in writing, software |
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
* See the License for the specific language governing permissions and |
||||||
|
* limitations under the License. |
||||||
|
*/ |
||||||
|
|
||||||
|
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet |
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column |
||||||
|
import androidx.compose.foundation.layout.ColumnScope |
||||||
|
import androidx.compose.foundation.layout.Spacer |
||||||
|
import androidx.compose.foundation.layout.height |
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api |
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState |
||||||
|
import androidx.compose.runtime.Composable |
||||||
|
import androidx.compose.runtime.rememberCoroutineScope |
||||||
|
import androidx.compose.ui.Modifier |
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter |
||||||
|
import androidx.compose.ui.unit.dp |
||||||
|
import io.element.android.features.messages.impl.timeline.model.receipts |
||||||
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize |
||||||
|
import io.element.android.libraries.designsystem.preview.ElementPreview |
||||||
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight |
||||||
|
import io.element.android.libraries.designsystem.theme.components.ModalBottomSheet |
||||||
|
import io.element.android.libraries.designsystem.theme.components.Text |
||||||
|
import io.element.android.libraries.matrix.api.core.UserId |
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser |
||||||
|
import io.element.android.libraries.matrix.ui.components.MatrixUserRow |
||||||
|
import io.element.android.libraries.theme.ElementTheme |
||||||
|
import kotlinx.coroutines.launch |
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class) |
||||||
|
@Composable |
||||||
|
internal fun ReadReceiptBottomSheetView( |
||||||
|
state: ReadReceiptBottomSheetState, |
||||||
|
modifier: Modifier = Modifier, |
||||||
|
) { |
||||||
|
val isVisible = state.selectedEvent != null |
||||||
|
|
||||||
|
val sheetState = rememberModalBottomSheetState() |
||||||
|
val coroutineScope = rememberCoroutineScope() |
||||||
|
if (isVisible) { |
||||||
|
ModalBottomSheet( |
||||||
|
modifier = modifier, |
||||||
|
// modifier = modifier.navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044 |
||||||
|
// .imePadding() |
||||||
|
sheetState = sheetState, |
||||||
|
onDismissRequest = { |
||||||
|
coroutineScope.launch { |
||||||
|
sheetState.hide() |
||||||
|
state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) |
||||||
|
} |
||||||
|
} |
||||||
|
) { |
||||||
|
ReadReceiptBottomSheetContents( |
||||||
|
state = state, |
||||||
|
) |
||||||
|
// FIXME remove after https://issuetracker.google.com/issues/275849044 |
||||||
|
Spacer(modifier = Modifier.height(32.dp)) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Composable |
||||||
|
private fun ColumnScope.ReadReceiptBottomSheetContents( |
||||||
|
state: ReadReceiptBottomSheetState, |
||||||
|
) { |
||||||
|
val receipts = state.selectedEvent?.readReceiptState?.receipts().orEmpty() |
||||||
|
receipts.forEach { |
||||||
|
MatrixUserRow( |
||||||
|
matrixUser = MatrixUser( |
||||||
|
UserId(it.avatarData.id), |
||||||
|
it.avatarData.name, |
||||||
|
it.avatarData.url, |
||||||
|
), |
||||||
|
avatarSize = AvatarSize.ReadReceiptList, |
||||||
|
trailingContent = { |
||||||
|
Text( |
||||||
|
text = it.formattedDate, |
||||||
|
style = ElementTheme.typography.fontBodySmRegular, |
||||||
|
color = ElementTheme.colors.textSecondary, |
||||||
|
) |
||||||
|
} |
||||||
|
) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@PreviewsDayNight |
||||||
|
@Composable |
||||||
|
internal fun ReadReceiptBottomSheetViewPreview(@PreviewParameter(ReadReceiptBottomSheetStateProvider::class) state: ReadReceiptBottomSheetState) = ElementPreview { |
||||||
|
// TODO restore RetrySendMessageMenuBottomSheet once the issue with bottom sheet not being previewable is fixed |
||||||
|
Column { |
||||||
|
ReadReceiptBottomSheetContents( |
||||||
|
state = state |
||||||
|
) |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue