From a132be2dd8e14a74d15048c999bf4f1b50ef1db9 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Wed, 13 Nov 2024 20:25:34 +0100 Subject: [PATCH 1/2] Use formatted captions for images and video (#3864) * Make `formattedCaption in `TimelineItemEventContentWithAttachment` a `Charsequence?`, parse the formatted caption body as we do for text message bodies * Add `TimelineItem.isWholeContentClickable` property to decide whether the click action should be triggered at the message bubble level or when some internal content is tapped instead. * Display the formatted/linkified captions in image and video timeline item views * Apply the `onClick` callback to the whole message bubble or only the content of the timeline item depending on `TimelineItem.isWholeContentClickable`. --- .../features/messages/impl/MessagesNode.kt | 2 +- .../features/messages/impl/MessagesView.kt | 14 +++++------ .../MessagesViewWithIdentityChangePreview.kt | 2 +- .../pinned/list/PinnedMessagesListView.kt | 7 ++++-- .../messages/impl/timeline/TimelineView.kt | 6 ++--- .../TimelineViewMessageShieldPreview.kt | 2 +- .../components/ATimelineItemEventRow.kt | 2 +- .../components/TimelineItemEventRow.kt | 19 ++++++++++----- .../TimelineItemGroupedEventsRow.kt | 8 ++++--- .../timeline/components/TimelineItemRow.kt | 16 +++++-------- .../components/TimelineItemStateEventRow.kt | 3 ++- .../event/TimelineItemEventContentView.kt | 15 ++++++++---- .../components/event/TimelineItemImageView.kt | 24 ++++++++++++------- .../event/TimelineItemLocationView.kt | 9 +++++-- .../event/TimelineItemStickerView.kt | 6 ++++- .../components/event/TimelineItemVideoView.kt | 24 ++++++++++++------- .../TimelineItemContentMessageFactory.kt | 14 +++++------ .../impl/timeline/model/TimelineItem.kt | 13 ++++++++++ .../model/event/TimelineItemAudioContent.kt | 3 +-- .../model/event/TimelineItemEventContent.kt | 3 +-- .../model/event/TimelineItemFileContent.kt | 3 +-- .../model/event/TimelineItemImageContent.kt | 3 +-- .../model/event/TimelineItemStickerContent.kt | 3 +-- .../model/event/TimelineItemVideoContent.kt | 3 +-- .../model/event/TimelineItemVoiceContent.kt | 3 +-- .../messages/impl/MessagesViewTest.kt | 2 +- .../impl/timeline/TimelineViewTest.kt | 2 +- .../TimelineItemContentMessageFactoryTest.kt | 4 ++-- 28 files changed, 129 insertions(+), 86 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt index bf76f208e3..9fd823445f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesNode.kt @@ -212,7 +212,7 @@ class MessagesNode @AssistedInject constructor( state = state, onBackClick = this::navigateUp, onRoomDetailsClick = this::onRoomDetailsClick, - onEventClick = this::onEventClick, + onEventContentClick = this::onEventClick, onPreviewAttachments = this::onPreviewAttachments, onUserDataClick = this::onUserDataClick, onLinkClick = { url -> onLinkClick(activity, isDark, url, state.timelineState.eventSink) }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index e5d73fbed6..3e2918b3ce 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -114,7 +114,7 @@ fun MessagesView( state: MessagesState, onBackClick: () -> Unit, onRoomDetailsClick: () -> Unit, - onEventClick: (event: TimelineItem.Event) -> Boolean, + onEventContentClick: (event: TimelineItem.Event) -> Boolean, onUserDataClick: (UserId) -> Unit, onLinkClick: (String) -> Unit, onPreviewAttachments: (ImmutableList) -> Unit, @@ -142,9 +142,9 @@ fun MessagesView( // This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose val localView = LocalView.current - fun onMessageClick(event: TimelineItem.Event) { + fun onContentClick(event: TimelineItem.Event) { Timber.v("onMessageClick= ${event.id}") - val hideKeyboard = onEventClick(event) + val hideKeyboard = onEventContentClick(event) if (hideKeyboard) { localView.hideKeyboard() } @@ -206,7 +206,7 @@ fun MessagesView( modifier = Modifier .padding(padding) .consumeWindowInsets(padding), - onMessageClick = ::onMessageClick, + onContentClick = ::onContentClick, onMessageLongClick = ::onMessageLongClick, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, @@ -306,7 +306,7 @@ private fun AttachmentStateView( @Composable private fun MessagesViewContent( state: MessagesState, - onMessageClick: (TimelineItem.Event) -> Unit, + onContentClick: (TimelineItem.Event) -> Unit, onUserDataClick: (UserId) -> Unit, onLinkClick: (String) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, @@ -382,7 +382,7 @@ private fun MessagesViewContent( timelineProtectionState = state.timelineProtectionState, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, - onMessageClick = onMessageClick, + onContentClick = onContentClick, onMessageLongClick = onMessageLongClick, onSwipeToReply = onSwipeToReply, onReactionClick = onReactionClick, @@ -568,7 +568,7 @@ internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) state = state, onBackClick = {}, onRoomDetailsClick = {}, - onEventClick = { false }, + onEventContentClick = { false }, onUserDataClick = {}, onLinkClick = {}, onPreviewAttachments = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt index c34f072c6d..04750b6ad8 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/crypto/identity/MessagesViewWithIdentityChangePreview.kt @@ -33,7 +33,7 @@ internal fun MessagesViewWithIdentityChangePreview( ), onBackClick = {}, onRoomDetailsClick = {}, - onEventClick = { false }, + onEventContentClick = { false }, onUserDataClick = {}, onLinkClick = {}, onPreviewAttachments = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index f4a3247cba..7ae88b9647 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -216,7 +216,7 @@ private fun PinnedMessagesListLoaded( focusedEventId = null, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, - onClick = onEventClick, + onContentClick = onEventClick, onLongClick = ::onMessageLongClick, inReplyToClick = {}, onReactionClick = { _, _ -> }, @@ -230,6 +230,7 @@ private fun PinnedMessagesListLoaded( TimelineItemEventContentViewWrapper( event = event, timelineProtectionState = state.timelineProtectionState, + onContentClick = { onEventClick(event) }, onLinkClick = onLinkClick, modifier = contentModifier, onContentLayoutChange = onContentLayoutChange @@ -244,6 +245,7 @@ private fun PinnedMessagesListLoaded( private fun TimelineItemEventContentViewWrapper( event: TimelineItem.Event, timelineProtectionState: TimelineProtectionState, + onContentClick: () -> Unit, onLinkClick: (String) -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, @@ -258,10 +260,11 @@ private fun TimelineItemEventContentViewWrapper( TimelineItemEventContentView( content = event.content, hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId), - onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, onLinkClick = onLinkClick, eventSink = { }, modifier = modifier, + onContentClick = onContentClick, onContentLayoutChange = onContentLayoutChange ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index f0e976e368..6e34bba9e1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -76,7 +76,7 @@ fun TimelineView( timelineProtectionState: TimelineProtectionState, onUserDataClick: (UserId) -> Unit, onLinkClick: (String) -> Unit, - onMessageClick: (TimelineItem.Event) -> Unit, + onContentClick: (TimelineItem.Event) -> Unit, onMessageLongClick: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit, onReactionClick: (emoji: String, TimelineItem.Event) -> Unit, @@ -141,7 +141,7 @@ fun TimelineView( focusedEventId = state.focusedEventId, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, - onClick = onMessageClick, + onContentClick = onContentClick, onLongClick = onMessageLongClick, inReplyToClick = ::inReplyToClick, onReactionClick = onReactionClick, @@ -322,7 +322,7 @@ internal fun TimelineViewPreview( timelineProtectionState = aTimelineProtectionState(), onUserDataClick = {}, onLinkClick = {}, - onMessageClick = {}, + onContentClick = {}, onMessageLongClick = {}, onSwipeToReply = {}, onReactionClick = { _, _ -> }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt index 99f69675fa..462e74137a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt @@ -41,7 +41,7 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview { timelineProtectionState = aTimelineProtectionState(), onUserDataClick = {}, onLinkClick = {}, - onMessageClick = {}, + onContentClick = {}, onMessageLongClick = {}, onSwipeToReply = {}, onReactionClick = { _, _ -> }, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt index 1fa5e7f9a1..85e9854919 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/ATimelineItemEventRow.kt @@ -30,7 +30,7 @@ internal fun ATimelineItemEventRow( timelineProtectionState = timelineProtectionState, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = isHighlighted, - onClick = {}, + onContentClick = {}, onLongClick = {}, onLinkClick = {}, onUserDataClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 2f0165cd7b..0061ebe6df 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -114,7 +114,7 @@ fun TimelineItemEventRow( renderReadReceipts: Boolean, isLastOutgoingMessage: Boolean, isHighlighted: Boolean, - onClick: () -> Unit, + onContentClick: () -> Unit, onLongClick: () -> Unit, onLinkClick: (String) -> Unit, onUserDataClick: (UserId) -> Unit, @@ -130,7 +130,8 @@ fun TimelineItemEventRow( TimelineItemEventContentView( content = event.content, hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId), - onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onContentClick = onContentClick, + onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, onLinkClick = onLinkClick, eventSink = eventSink, modifier = contentModifier, @@ -150,6 +151,12 @@ fun TimelineItemEventRow( inReplyToClick(inReplyToEventId) } + val onWholeItemClick = if (event.isWholeContentClickable) { + onContentClick + } else { + {} + } + Column(modifier = modifier.fillMaxWidth()) { if (event.groupPosition.isNew()) { Spacer(modifier = Modifier.height(16.dp)) @@ -173,7 +180,7 @@ fun TimelineItemEventRow( isHighlighted = isHighlighted, timelineRoomInfo = timelineRoomInfo, interactionSource = interactionSource, - onClick = onClick, + onContentClick = onWholeItemClick, onLongClick = onLongClick, inReplyToClick = ::inReplyToClick, onUserDataClick = ::onUserDataClick, @@ -207,7 +214,7 @@ fun TimelineItemEventRow( isHighlighted = isHighlighted, timelineRoomInfo = timelineRoomInfo, interactionSource = interactionSource, - onClick = onClick, + onContentClick = onWholeItemClick, onLongClick = onLongClick, inReplyToClick = ::inReplyToClick, onUserDataClick = ::onUserDataClick, @@ -263,7 +270,7 @@ private fun TimelineItemEventRowContent( isHighlighted: Boolean, timelineRoomInfo: TimelineRoomInfo, interactionSource: MutableInteractionSource, - onClick: () -> Unit, + onContentClick: () -> Unit, onLongClick: () -> Unit, inReplyToClick: () -> Unit, onUserDataClick: () -> Unit, @@ -340,7 +347,7 @@ private fun TimelineItemEventRowContent( }, state = bubbleState, interactionSource = interactionSource, - onClick = onClick, + onClick = onContentClick, onLongClick = onLongClick, ) { MessageEventBubbleContent( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt index d280efa62c..fe89d6de02 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemGroupedEventsRow.kt @@ -57,10 +57,11 @@ fun TimelineItemGroupedEventsRow( TimelineItemEventContentView( content = event.content, hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId), - onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, onLinkClick = onLinkClick, eventSink = eventSink, modifier = contentModifier, + onContentClick = {}, onContentLayoutChange = onContentLayoutChange ) }, @@ -121,10 +122,11 @@ private fun TimelineItemGroupedEventsRowContent( TimelineItemEventContentView( content = event.content, hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId), - onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, onLinkClick = onLinkClick, eventSink = eventSink, modifier = contentModifier, + onContentClick = {}, onContentLayoutChange = onContentLayoutChange ) }, @@ -152,7 +154,7 @@ private fun TimelineItemGroupedEventsRowContent( focusedEventId = focusedEventId, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, - onClick = onClick, + onContentClick = onClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, onReactionClick = onReactionClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt index 13c247645b..047f9b3d04 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemRow.kt @@ -28,7 +28,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionEvent import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState -import io.element.android.features.messages.impl.timeline.protection.mustBeProtected import io.element.android.libraries.designsystem.text.toPx import io.element.android.libraries.designsystem.theme.highlightedMessageBackgroundColor import io.element.android.libraries.matrix.api.core.EventId @@ -44,7 +43,7 @@ internal fun TimelineItemRow( focusedEventId: EventId?, onUserDataClick: (UserId) -> Unit, onLinkClick: (String) -> Unit, - onClick: (TimelineItem.Event) -> Unit, + onContentClick: (TimelineItem.Event) -> Unit, onLongClick: (TimelineItem.Event) -> Unit, inReplyToClick: (EventId) -> Unit, onReactionClick: (key: String, TimelineItem.Event) -> Unit, @@ -60,7 +59,8 @@ internal fun TimelineItemRow( TimelineItemEventContentView( content = event.content, hideMediaContent = timelineProtectionState.hideMediaContent(event.eventId), - onShowClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onShowContentClick = { timelineProtectionState.eventSink(TimelineProtectionEvent.ShowContent(event.eventId)) }, + onContentClick = { onContentClick(event) }, onLinkClick = onLinkClick, eventSink = eventSink, modifier = contentModifier, @@ -95,7 +95,7 @@ internal fun TimelineItemRow( renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = timelineItem.isEvent(focusedEventId), - onClick = { onClick(timelineItem) }, + onClick = { onContentClick(timelineItem) }, onReadReceiptsClick = onReadReceiptClick, onLongClick = { onLongClick(timelineItem) }, eventSink = eventSink, @@ -118,11 +118,7 @@ internal fun TimelineItemRow( timelineProtectionState = timelineProtectionState, isLastOutgoingMessage = isLastOutgoingMessage, isHighlighted = timelineItem.isEvent(focusedEventId), - onClick = if (timelineProtectionState.hideMediaContent(timelineItem.eventId) && timelineItem.mustBeProtected()) { - {} - } else { - { onClick(timelineItem) } - }, + onContentClick = { onContentClick(timelineItem) }, onLongClick = { onLongClick(timelineItem) }, onLinkClick = onLinkClick, onUserDataClick = onUserDataClick, @@ -148,7 +144,7 @@ internal fun TimelineItemRow( renderReadReceipts = renderReadReceipts, isLastOutgoingMessage = isLastOutgoingMessage, focusedEventId = focusedEventId, - onClick = onClick, + onClick = onContentClick, onLongClick = onLongClick, inReplyToClick = inReplyToClick, onUserDataClick = onUserDataClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt index f023a63c35..6172686016 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemStateEventRow.kt @@ -72,8 +72,9 @@ fun TimelineItemStateEventRow( content = event.content, onLinkClick = {}, hideMediaContent = false, - onShowClick = {}, + onShowContentClick = {}, eventSink = eventSink, + onContentClick = {}, modifier = Modifier.defaultTimelineContentPadding() ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt index b24a5ca0be..3a3a43a6fc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemEventContentView.kt @@ -36,7 +36,8 @@ import io.element.android.libraries.architecture.Presenter fun TimelineItemEventContentView( content: TimelineItemEventContent, hideMediaContent: Boolean, - onShowClick: () -> Unit, + onContentClick: () -> Unit, + onShowContentClick: () -> Unit, onLinkClick: (url: String) -> Unit, eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, modifier: Modifier = Modifier, @@ -67,25 +68,31 @@ fun TimelineItemEventContentView( ) is TimelineItemLocationContent -> TimelineItemLocationView( content = content, + onContentClick = onContentClick, modifier = modifier ) is TimelineItemImageContent -> TimelineItemImageView( content = content, hideMediaContent = hideMediaContent, - onShowClick = onShowClick, + onContentClick = onContentClick, + onShowContentClick = onShowContentClick, + onLinkClick = onLinkClick, onContentLayoutChange = onContentLayoutChange, modifier = modifier, ) is TimelineItemStickerContent -> TimelineItemStickerView( content = content, hideMediaContent = hideMediaContent, - onShowClick = onShowClick, + onContentClick = onContentClick, + onShowClick = onShowContentClick, modifier = modifier, ) is TimelineItemVideoContent -> TimelineItemVideoView( content = content, hideMediaContent = hideMediaContent, - onShowClick = onShowClick, + onContentClick = onContentClick, + onShowContentClick = onShowContentClick, + onLinkClick = onLinkClick, onContentLayoutChange = onContentLayoutChange, modifier = modifier ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt index 63bbac7f6f..123ba195f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemImageView.kt @@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline.components.event import android.text.SpannedString import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -50,7 +51,6 @@ import io.element.android.features.messages.impl.timeline.protection.ProtectedVi import io.element.android.libraries.designsystem.components.blurhash.blurHashBackground import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.textcomposer.ElementRichTextEditorStyle import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.wysiwyg.compose.EditorStyledText @@ -59,7 +59,9 @@ import io.element.android.wysiwyg.compose.EditorStyledText fun TimelineItemImageView( content: TimelineItemImageContent, hideMediaContent: Boolean, - onShowClick: () -> Unit, + onContentClick: () -> Unit, + onLinkClick: (String) -> Unit, + onShowContentClick: () -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, ) { @@ -78,13 +80,14 @@ fun TimelineItemImageView( ) { ProtectedView( hideContent = hideMediaContent, - onShowClick = onShowClick, + onShowClick = onShowContentClick, ) { var isLoaded by remember { mutableStateOf(false) } AsyncImage( modifier = Modifier .fillMaxWidth() - .then(if (isLoaded) Modifier.background(Color.White) else Modifier), + .then(if (isLoaded) Modifier.background(Color.White) else Modifier) + .clickable(onClick = onContentClick), model = content.thumbnailMediaRequestData, contentScale = ContentScale.Fit, alignment = Alignment.Center, @@ -99,9 +102,7 @@ fun TimelineItemImageView( val caption = if (LocalInspectionMode.current) { SpannedString(content.caption) } else { - content.formattedCaption?.body - ?.takeIf { content.formattedCaption.format == MessageFormat.HTML } - ?: SpannedString(content.caption) + content.formattedCaption ?: SpannedString(content.caption) } CompositionLocalProvider( LocalContentColor provides ElementTheme.colors.textPrimary, @@ -114,6 +115,7 @@ fun TimelineItemImageView( .widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio), text = caption, style = ElementRichTextEditorStyle.textStyle(), + onLinkClickedListener = onLinkClick, releaseOnDetach = false, onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange), ) @@ -128,7 +130,9 @@ internal fun TimelineItemImageViewPreview(@PreviewParameter(TimelineItemImageCon TimelineItemImageView( content = content, hideMediaContent = false, - onShowClick = {}, + onShowContentClick = {}, + onContentClick = {}, + onLinkClick = {}, onContentLayoutChange = {}, ) } @@ -139,7 +143,9 @@ internal fun TimelineItemImageViewHideMediaContentPreview() = ElementPreview { TimelineItemImageView( content = aTimelineItemImageContent(), hideMediaContent = true, - onShowClick = {}, + onShowContentClick = {}, + onContentClick = {}, + onLinkClick = {}, onContentLayoutChange = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt index 91cfcff1d8..5d402bc8f1 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemLocationView.kt @@ -7,6 +7,7 @@ package io.element.android.features.messages.impl.timeline.components.event +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn @@ -25,9 +26,10 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun TimelineItemLocationView( content: TimelineItemLocationContent, + onContentClick: () -> Unit, modifier: Modifier = Modifier, ) { - Column(modifier = modifier.fillMaxWidth()) { + Column(modifier = modifier.clickable(onClick = onContentClick).fillMaxWidth()) { content.description?.let { Text( text = it, @@ -51,5 +53,8 @@ fun TimelineItemLocationView( @Composable internal fun TimelineItemLocationViewPreview(@PreviewParameter(TimelineItemLocationContentProvider::class) content: TimelineItemLocationContent) = ElementPreview { - TimelineItemLocationView(content) + TimelineItemLocationView( + content = content, + onContentClick = {}, + ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt index 0574843dba..df087769ed 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemStickerView.kt @@ -8,6 +8,7 @@ package io.element.android.features.messages.impl.timeline.components.event import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -40,6 +41,7 @@ private const val STICKER_SIZE_IN_DP = 128 fun TimelineItemStickerView( content: TimelineItemStickerContent, hideMediaContent: Boolean, + onContentClick: () -> Unit, onShowClick: () -> Unit, modifier: Modifier = Modifier, ) { @@ -61,7 +63,8 @@ fun TimelineItemStickerView( AsyncImage( modifier = Modifier .fillMaxSize() - .then(if (isLoaded) Modifier.background(Color.White) else Modifier), + .then(if (isLoaded) Modifier.background(Color.White) else Modifier) + .clickable(onClick = onContentClick), model = MediaRequestData( source = content.preferredMediaSource, kind = MediaRequestData.Kind.File( @@ -85,6 +88,7 @@ internal fun TimelineItemStickerViewPreview(@PreviewParameter(TimelineItemSticke TimelineItemStickerView( content = content, hideMediaContent = false, + onContentClick = {}, onShowClick = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt index 64e6d00d71..65f59cd631 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/event/TimelineItemVideoView.kt @@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.event import android.text.SpannedString import androidx.compose.foundation.Image import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -56,7 +57,6 @@ import io.element.android.libraries.designsystem.components.blurhash.blurHashBac import io.element.android.libraries.designsystem.modifiers.roundedBackground import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight -import io.element.android.libraries.matrix.api.timeline.item.event.MessageFormat import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_HEIGHT import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH import io.element.android.libraries.matrix.ui.media.MediaRequestData @@ -68,7 +68,9 @@ import io.element.android.wysiwyg.compose.EditorStyledText fun TimelineItemVideoView( content: TimelineItemVideoContent, hideMediaContent: Boolean, - onShowClick: () -> Unit, + onContentClick: () -> Unit, + onShowContentClick: () -> Unit, + onLinkClick: (String) -> Unit, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, modifier: Modifier = Modifier, ) { @@ -90,13 +92,14 @@ fun TimelineItemVideoView( ) { ProtectedView( hideContent = hideMediaContent, - onShowClick = onShowClick, + onShowClick = onShowContentClick, ) { var isLoaded by remember { mutableStateOf(false) } AsyncImage( modifier = Modifier .fillMaxWidth() - .then(if (isLoaded) Modifier.background(Color.White) else Modifier), + .then(if (isLoaded) Modifier.background(Color.White) else Modifier) + .clickable(onClick = onContentClick), model = MediaRequestData( source = content.thumbnailSource, kind = MediaRequestData.Kind.Thumbnail( @@ -128,9 +131,7 @@ fun TimelineItemVideoView( val caption = if (LocalInspectionMode.current) { SpannedString(content.caption) } else { - content.formattedCaption?.body - ?.takeIf { content.formattedCaption.format == MessageFormat.HTML } - ?: SpannedString(content.caption) + content.formattedCaption ?: SpannedString(content.caption) } CompositionLocalProvider( LocalContentColor provides ElementTheme.colors.textPrimary, @@ -142,6 +143,7 @@ fun TimelineItemVideoView( .padding(horizontal = 4.dp) // This is (12.dp - 8.dp) contentPadding from CommonLayout .widthIn(min = MIN_HEIGHT_IN_DP.dp * aspectRatio, max = MAX_HEIGHT_IN_DP.dp * aspectRatio), text = caption, + onLinkClickedListener = onLinkClick, style = ElementRichTextEditorStyle.textStyle(), releaseOnDetach = false, onTextLayout = ContentAvoidingLayout.measureLegacyLastTextLine(onContentLayoutChange = onContentLayoutChange), @@ -157,7 +159,9 @@ internal fun TimelineItemVideoViewPreview(@PreviewParameter(TimelineItemVideoCon TimelineItemVideoView( content = content, hideMediaContent = false, - onShowClick = {}, + onShowContentClick = {}, + onContentClick = {}, + onLinkClick = {}, onContentLayoutChange = {}, ) } @@ -168,7 +172,9 @@ internal fun TimelineItemVideoViewHideMediaContentPreview() = ElementPreview { TimelineItemVideoView( content = aTimelineItemVideoContent(), hideMediaContent = true, - onShowClick = {}, + onShowContentClick = {}, + onContentClick = {}, + onLinkClick = {}, onContentLayoutChange = {}, ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt index 001d40b254..ff5ca57c11 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactory.kt @@ -86,7 +86,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemImageContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -105,7 +105,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemStickerContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source, thumbnailSource = messageType.info?.thumbnailSource, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -142,7 +142,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemVideoContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), thumbnailSource = messageType.info?.thumbnailSource, videoSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -161,7 +161,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemAudioContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -176,7 +176,7 @@ class TimelineItemContentMessageFactory @Inject constructor( eventId = eventId, filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -187,7 +187,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemAudioContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source, duration = messageType.info?.duration ?: Duration.ZERO, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, @@ -202,7 +202,7 @@ class TimelineItemContentMessageFactory @Inject constructor( TimelineItemFileContent( filename = messageType.filename, caption = messageType.caption?.trimEnd(), - formattedCaption = messageType.formattedCaption, + formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), thumbnailSource = messageType.info?.thumbnailSource, fileSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.fromFileExtension(fileExtension), diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt index dcf0e16aa8..e2c81a3794 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/TimelineItem.kt @@ -9,8 +9,10 @@ package io.element.android.features.messages.impl.timeline.model import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStickerContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent +import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.matrix.api.core.EventId @@ -93,6 +95,17 @@ sealed interface TimelineItem { val isRemote = eventId != null + /** Whether a click on any part of the event bubble should trigger the 'onContentClick' callback. + * + * This is `true` for all events except for visual media events with a caption or formatted caption. + */ + val isWholeContentClickable = when (content) { + is TimelineItemStickerContent -> content.formattedCaption == null && content.caption == null + is TimelineItemImageContent -> content.formattedCaption == null && content.caption == null + is TimelineItemVideoContent -> content.formattedCaption == null && content.caption == null + else -> true + } + val eventOrTransactionId: EventOrTransactionId get() = EventOrTransactionId.from(eventId = eventId, transactionId = transactionId) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt index fa2957b860..0ecf45ec55 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemAudioContent.kt @@ -8,14 +8,13 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize import kotlin.time.Duration data class TimelineItemAudioContent( override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val duration: Duration, val mediaSource: MediaSource, val mimeType: String, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt index 10204a75d8..fafadcbf79 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemEventContent.kt @@ -8,7 +8,6 @@ package io.element.android.features.messages.impl.timeline.model.event import androidx.compose.runtime.Immutable -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody @Immutable sealed interface TimelineItemEventContent { @@ -19,7 +18,7 @@ sealed interface TimelineItemEventContent { sealed interface TimelineItemEventContentWithAttachment : TimelineItemEventContent { val filename: String val caption: String? - val formattedCaption: FormattedBody? + val formattedCaption: CharSequence? val bestDescription: String get() = caption ?: filename diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt index f11325727c..b7007c8bbc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemFileContent.kt @@ -8,13 +8,12 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.mediaviewer.api.helper.formatFileExtensionAndSize data class TimelineItemFileContent( override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val fileSource: MediaSource, val thumbnailSource: MediaSource?, val formattedFileSize: String, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt index e6e4bffb9b..b13c7a4e37 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemImageContent.kt @@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAnimatedImage import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_HEIGHT import io.element.android.libraries.matrix.ui.media.MAX_THUMBNAIL_WIDTH import io.element.android.libraries.matrix.ui.media.MediaRequestData @@ -17,7 +16,7 @@ import io.element.android.libraries.matrix.ui.media.MediaRequestData data class TimelineItemImageContent( override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val mediaSource: MediaSource, val thumbnailSource: MediaSource?, val formattedFileSize: String, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt index 3476d97e2f..06886307ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemStickerContent.kt @@ -8,12 +8,11 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody data class TimelineItemStickerContent( override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val mediaSource: MediaSource, val thumbnailSource: MediaSource?, val formattedFileSize: String, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt index 5c0e601708..486a71b5d4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVideoContent.kt @@ -8,13 +8,12 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import kotlin.time.Duration data class TimelineItemVideoContent( override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val duration: Duration, val videoSource: MediaSource, val thumbnailSource: MediaSource?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt index 853cc01045..ebeabef715 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/event/TimelineItemVoiceContent.kt @@ -9,7 +9,6 @@ package io.element.android.features.messages.impl.timeline.model.event import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.media.MediaSource -import io.element.android.libraries.matrix.api.timeline.item.event.FormattedBody import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration @@ -17,7 +16,7 @@ data class TimelineItemVoiceContent( val eventId: EventId?, override val filename: String, override val caption: String?, - override val formattedCaption: FormattedBody?, + override val formattedCaption: CharSequence?, val duration: Duration, val mediaSource: MediaSource, val mimeType: String, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt index a20acaa5df..c040ce9926 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesViewTest.kt @@ -529,7 +529,7 @@ private fun AndroidComposeTestRule.setMessa state = state, onBackClick = onBackClick, onRoomDetailsClick = onRoomDetailsClick, - onEventClick = onEventClick, + onEventContentClick = onEventClick, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, onPreviewAttachments = onPreviewAttachments, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 315abd8302..6a7b49c34c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -158,7 +158,7 @@ private fun AndroidComposeTestRule.setTimel timelineProtectionState = timelineProtectionState, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, - onMessageClick = onMessageClick, + onContentClick = onMessageClick, onMessageLongClick = onMessageLongClick, onSwipeToReply = onSwipeToReply, onReactionClick = onReactionClick, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index e9343bf21d..a34e601e5d 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -286,7 +286,7 @@ class TimelineItemContentMessageFactoryTest { val expected = TimelineItemVideoContent( filename = "body.mp4", caption = "body.mp4 caption", - formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"), + formattedCaption = SpannedString("formatted"), duration = 1.minutes, videoSource = MediaSource(url = "url", json = null), thumbnailSource = MediaSource("url_thumbnail"), @@ -527,7 +527,7 @@ class TimelineItemContentMessageFactoryTest { ) val expected = TimelineItemImageContent( filename = "body.jpg", - formattedCaption = FormattedBody(MessageFormat.HTML, "formatted"), + formattedCaption = SpannedString("formatted"), caption = "body.jpg caption", mediaSource = MediaSource(url = "url", json = null), thumbnailSource = MediaSource("url_thumbnail"), From 4c61e5dfa23bbba5870da85bb4e52f3f8cb91bb1 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Thu, 14 Nov 2024 09:06:13 +0100 Subject: [PATCH 2/2] Remove :samples:minimal module --- .github/workflows/build.yml | 5 +- CONTRIBUTING.md | 2 - docs/continuous_integration.md | 2 +- .../main/kotlin/extension/KoverExtension.kt | 2 - ...ent.android-compose-application.gradle.kts | 2 +- samples/minimal/.gitignore | 1 - samples/minimal/build.gradle.kts | 67 ------- samples/minimal/src/main/AndroidManifest.xml | 27 --- .../AlwaysEnabledFeatureFlagService.kt | 23 --- .../android/samples/minimal/LoginScreen.kt | 43 ----- .../android/samples/minimal/MainActivity.kt | 95 ---------- .../samples/minimal/NoOpProxyProvider.kt | 14 -- .../minimal/NoOpUserCertificatesProvider.kt | 14 -- .../minimal/NullPassphraseGenerator.kt | 14 -- .../minimal/OnlyFallbackPermalinkParser.kt | 18 -- .../android/samples/minimal/RoomListScreen.kt | 174 ------------------ .../android/samples/minimal/Singleton.kt | 55 ------ .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 1404 -> 0 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 2898 -> 0 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 982 -> 0 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 1772 -> 0 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 1900 -> 0 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 3918 -> 0 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 2884 -> 0 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 5914 -> 0 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 3844 -> 0 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 7778 -> 0 bytes .../src/main/res/values-night/themes.xml | 10 - .../minimal/src/main/res/values/strings.xml | 9 - .../minimal/src/main/res/values/themes.xml | 10 - settings.gradle.kts | 2 - tools/quality/check.sh | 1 - 32 files changed, 3 insertions(+), 587 deletions(-) delete mode 100644 samples/minimal/.gitignore delete mode 100644 samples/minimal/build.gradle.kts delete mode 100644 samples/minimal/src/main/AndroidManifest.xml delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/AlwaysEnabledFeatureFlagService.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpProxyProvider.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt delete mode 100644 samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt delete mode 100644 samples/minimal/src/main/res/mipmap-hdpi/ic_launcher.webp delete mode 100644 samples/minimal/src/main/res/mipmap-hdpi/ic_launcher_round.webp delete mode 100644 samples/minimal/src/main/res/mipmap-mdpi/ic_launcher.webp delete mode 100644 samples/minimal/src/main/res/mipmap-mdpi/ic_launcher_round.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher_round.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xxhdpi/ic_launcher.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xxxhdpi/ic_launcher.webp delete mode 100644 samples/minimal/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp delete mode 100644 samples/minimal/src/main/res/values-night/themes.xml delete mode 100644 samples/minimal/src/main/res/values/strings.xml delete mode 100644 samples/minimal/src/main/res/values/themes.xml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 39e4364658..aef924c392 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,7 +18,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - variant: [debug, release, nightly, samples] + variant: [debug, release, nightly] fail-fast: false # Allow all jobs on develop. Just one per PR. concurrency: @@ -82,6 +82,3 @@ jobs: - name: Compile nightly sources if: ${{ matrix.variant == 'nightly' }} run: ./gradlew compileGplayNightlySources -PallWarningsAsErrors=true $CI_GRADLE_ARG_PROPERTIES - - name: Compile samples minimal - if: ${{ matrix.variant == 'samples' }} - run: ./gradlew :samples:minimal:assemble $CI_GRADLE_ARG_PROPERTIES diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6acfa7981f..bc4acfd5aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -49,8 +49,6 @@ Please ensure that you're using the project formatting rules (which are in the p This project should compile without any special action. Just clone it and open it with Android Studio, or compile from command line using `gradlew`. -Note: please make sure that the configuration is `app` and not `samples.minimal`. - ## Strings The strings of the project are managed externally using [https://localazy.com](https://localazy.com) and shared with Element X iOS. diff --git a/docs/continuous_integration.md b/docs/continuous_integration.md index 4ff0ebb983..85a869cbe9 100644 --- a/docs/continuous_integration.md +++ b/docs/continuous_integration.md @@ -40,7 +40,7 @@ We want: The CI checks that: -1. The code is compiling, without any warnings, for all the app build types and variants and for the minimal app +1. The code is compiling, without any warnings, for all the app build types and variants 2. The tests are passing 3. The code quality is good (detekt, ktlint, lint) 4. The code is running and smoke tests are passing (maestro) diff --git a/plugins/src/main/kotlin/extension/KoverExtension.kt b/plugins/src/main/kotlin/extension/KoverExtension.kt index 6182fde1f5..ccc077b0a3 100644 --- a/plugins/src/main/kotlin/extension/KoverExtension.kt +++ b/plugins/src/main/kotlin/extension/KoverExtension.kt @@ -32,10 +32,8 @@ val localAarProjects = listOf( val excludedKoverSubProjects = listOf( ":app", - ":samples", ":anvilannotations", ":anvilcodegen", - ":samples:minimal", ":tests:testutils", // Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix // SDK api, so it is not really relevant to unit test it: there is no logic to test. diff --git a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts index 967e4fd707..579bda8873 100644 --- a/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts +++ b/plugins/src/main/kotlin/io.element.android-compose-application.gradle.kts @@ -6,7 +6,7 @@ */ /** - * This will generate the plugin "io.element.android-compose-application" to use by app and samples modules + * This will generate the plugin "io.element.android-compose-application" to use by app */ import extension.androidConfig import extension.commonDependencies diff --git a/samples/minimal/.gitignore b/samples/minimal/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/samples/minimal/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/samples/minimal/build.gradle.kts b/samples/minimal/build.gradle.kts deleted file mode 100644 index 48713af442..0000000000 --- a/samples/minimal/build.gradle.kts +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ -plugins { - id("io.element.android-compose-application") - alias(libs.plugins.kotlin.android) -} - -android { - namespace = "io.element.android.samples.minimal" - - defaultConfig { - applicationId = "io.element.android.samples.minimal" - targetSdk = Versions.TARGET_SDK - versionCode = Versions.VERSION_CODE - versionName = Versions.VERSION_NAME - testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" - } - - buildFeatures { - buildConfig = true - } - - buildTypes { - release { - isMinifyEnabled = false - proguardFiles( - getDefaultProguardFile("proguard-android-optimize.txt"), - "proguard-rules.pro" - ) - } - } -} - -dependencies { - implementation(libs.androidx.activity.compose) - implementation(libs.androidx.preference) - implementation(projects.libraries.matrix.api) - implementation(projects.libraries.matrix.impl) - implementation(projects.libraries.permissions.noop) - implementation(projects.libraries.sessionStorage.implMemory) - implementation(projects.libraries.designsystem) - implementation(projects.libraries.androidutils) - implementation(projects.libraries.architecture) - implementation(projects.libraries.core) - implementation(projects.libraries.network) - implementation(projects.libraries.dateformatter.impl) - implementation(projects.libraries.eventformatter.impl) - implementation(projects.libraries.fullscreenintent.impl) - implementation(projects.libraries.preferences.impl) - implementation(projects.libraries.preferences.test) - implementation(projects.libraries.indicator.impl) - implementation(projects.features.invite.impl) - implementation(projects.features.roomlist.impl) - implementation(projects.features.leaveroom.impl) - implementation(projects.features.login.impl) - implementation(projects.features.logout.impl) - implementation(projects.features.networkmonitor.impl) - implementation(projects.services.toolbox.impl) - implementation(projects.libraries.featureflag.impl) - implementation(projects.services.analytics.noop) - implementation(libs.coroutines.core) - implementation(projects.libraries.push.test) -} diff --git a/samples/minimal/src/main/AndroidManifest.xml b/samples/minimal/src/main/AndroidManifest.xml deleted file mode 100644 index 3f189bf096..0000000000 --- a/samples/minimal/src/main/AndroidManifest.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/AlwaysEnabledFeatureFlagService.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/AlwaysEnabledFeatureFlagService.kt deleted file mode 100644 index 67151d5aa4..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/AlwaysEnabledFeatureFlagService.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import io.element.android.libraries.featureflag.api.Feature -import io.element.android.libraries.featureflag.api.FeatureFlagService -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf - -class AlwaysEnabledFeatureFlagService : FeatureFlagService { - override fun isFeatureEnabledFlow(feature: Feature): Flow { - return flowOf(true) - } - - override suspend fun setFeatureEnabled(feature: Feature, enabled: Boolean): Boolean { - return true - } -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt deleted file mode 100644 index 1e4324d99f..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/LoginScreen.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import io.element.android.features.login.impl.DefaultLoginUserStory -import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource -import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordPresenter -import io.element.android.features.login.impl.screens.loginpassword.LoginPasswordView -import io.element.android.features.login.impl.util.defaultAccountProvider -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService - -class LoginScreen(private val authenticationService: MatrixAuthenticationService) { - @Composable - fun Content(modifier: Modifier = Modifier) { - val presenter = remember { - LoginPasswordPresenter( - authenticationService = authenticationService, - AccountProviderDataSource(), - DefaultLoginUserStory(), - ) - } - - LaunchedEffect(Unit) { - authenticationService.setHomeserver(defaultAccountProvider.url) - } - - val state = presenter.present() - LoginPasswordView( - state = state, - modifier = modifier, - onBackClick = {}, - ) - } -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt deleted file mode 100644 index 1a3c68b478..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.core.view.WindowCompat -import io.element.android.compound.theme.ElementTheme -import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService -import io.element.android.libraries.matrix.impl.RustClientBuilderProvider -import io.element.android.libraries.matrix.impl.RustMatrixClientFactory -import io.element.android.libraries.matrix.impl.auth.OidcConfigurationProvider -import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService -import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory -import io.element.android.libraries.matrix.impl.room.RustTimelineEventTypeFilterFactory -import io.element.android.libraries.network.useragent.SimpleUserAgentProvider -import io.element.android.libraries.preferences.test.InMemoryAppPreferencesStore -import io.element.android.libraries.sessionstorage.api.LoggedInState -import io.element.android.libraries.sessionstorage.impl.memory.InMemorySessionStore -import io.element.android.services.analytics.noop.NoopAnalyticsService -import io.element.android.services.toolbox.impl.systemclock.DefaultSystemClock -import kotlinx.coroutines.runBlocking -import java.io.File - -class MainActivity : ComponentActivity() { - private val matrixAuthenticationService: MatrixAuthenticationService by lazy { - val baseDirectory = File(applicationContext.filesDir, "sessions") - val userAgentProvider = SimpleUserAgentProvider("MinimalSample") - val sessionStore = InMemorySessionStore() - val userCertificatesProvider = NoOpUserCertificatesProvider() - val proxyProvider = NoOpProxyProvider() - RustMatrixAuthenticationService( - sessionPathsFactory = SessionPathsFactory(baseDirectory, applicationContext.cacheDir), - coroutineDispatchers = Singleton.coroutineDispatchers, - sessionStore = sessionStore, - rustMatrixClientFactory = RustMatrixClientFactory( - baseDirectory = baseDirectory, - cacheDirectory = applicationContext.cacheDir, - appCoroutineScope = Singleton.appScope, - coroutineDispatchers = Singleton.coroutineDispatchers, - sessionStore = sessionStore, - userAgentProvider = userAgentProvider, - userCertificatesProvider = userCertificatesProvider, - proxyProvider = proxyProvider, - clock = DefaultSystemClock(), - analyticsService = NoopAnalyticsService(), - featureFlagService = AlwaysEnabledFeatureFlagService(), - timelineEventTypeFilterFactory = RustTimelineEventTypeFilterFactory(), - clientBuilderProvider = RustClientBuilderProvider(), - ), - passphraseGenerator = NullPassphraseGenerator(), - oidcConfigurationProvider = OidcConfigurationProvider(baseDirectory), - appPreferencesStore = InMemoryAppPreferencesStore(), - ) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - WindowCompat.setDecorFitsSystemWindows(window, false) - setContent { - ElementTheme { - val loggedInState by matrixAuthenticationService.loggedInStateFlow().collectAsState(initial = LoggedInState.NotLoggedIn) - Content(isLoggedIn = loggedInState is LoggedInState.LoggedIn, modifier = Modifier.fillMaxSize()) - } - } - } - - @Composable - fun Content( - isLoggedIn: Boolean, - modifier: Modifier = Modifier - ) { - if (!isLoggedIn) { - LoginScreen(authenticationService = matrixAuthenticationService).Content(modifier) - } else { - val matrixClient = runBlocking { - val sessionId = matrixAuthenticationService.getLatestSessionId()!! - matrixAuthenticationService.restoreSession(sessionId).getOrNull() - } - RoomListScreen(LocalContext.current, matrixClient!!).Content(modifier) - } - } -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpProxyProvider.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpProxyProvider.kt deleted file mode 100644 index cda2f870ce..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpProxyProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import io.element.android.libraries.matrix.impl.proxy.ProxyProvider - -class NoOpProxyProvider : ProxyProvider { - override fun provides(): String? = null -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt deleted file mode 100644 index fbaac27e79..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NoOpUserCertificatesProvider.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import io.element.android.libraries.matrix.impl.certificates.UserCertificatesProvider - -class NoOpUserCertificatesProvider : UserCertificatesProvider { - override fun provides(): List = emptyList() -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt deleted file mode 100644 index 2ee4cbd4e5..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/NullPassphraseGenerator.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import io.element.android.libraries.matrix.impl.keys.PassphraseGenerator - -class NullPassphraseGenerator : PassphraseGenerator { - override fun generatePassphrase(): String? = null -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt deleted file mode 100644 index 254bd893c8..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/OnlyFallbackPermalinkParser.kt +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import android.net.Uri -import io.element.android.libraries.matrix.api.permalink.PermalinkData -import io.element.android.libraries.matrix.api.permalink.PermalinkParser - -class OnlyFallbackPermalinkParser : PermalinkParser { - override fun parse(uriString: String): PermalinkData { - return PermalinkData.FallbackLink(Uri.parse(uriString)) - } -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt deleted file mode 100644 index 0db3c9da51..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import android.content.Context -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.Modifier -import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter -import io.element.android.features.invite.impl.response.AcceptDeclineInviteView -import io.element.android.features.leaveroom.impl.LeaveRoomPresenter -import io.element.android.features.logout.impl.direct.DirectLogoutPresenter -import io.element.android.features.networkmonitor.impl.DefaultNetworkMonitor -import io.element.android.features.roomlist.impl.RoomListPresenter -import io.element.android.features.roomlist.impl.RoomListView -import io.element.android.features.roomlist.impl.datasource.RoomListDataSource -import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory -import io.element.android.features.roomlist.impl.filters.RoomListFiltersPresenter -import io.element.android.features.roomlist.impl.filters.selection.DefaultFilterSelectionStrategy -import io.element.android.features.roomlist.impl.search.RoomListSearchDataSource -import io.element.android.features.roomlist.impl.search.RoomListSearchPresenter -import io.element.android.libraries.androidutils.system.DefaultDateTimeObserver -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.dateformatter.impl.DateFormatters -import io.element.android.libraries.dateformatter.impl.DefaultLastMessageTimestampFormatter -import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider -import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher -import io.element.android.libraries.eventformatter.impl.DefaultRoomLastMessageFormatter -import io.element.android.libraries.eventformatter.impl.ProfileChangeContentFormatter -import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFormatter -import io.element.android.libraries.eventformatter.impl.StateContentFormatter -import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState -import io.element.android.libraries.indicator.impl.DefaultIndicatorService -import io.element.android.libraries.matrix.api.MatrixClient -import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.room.RoomMembershipObserver -import io.element.android.libraries.matrix.api.timeline.Timeline -import io.element.android.libraries.matrix.impl.room.join.DefaultJoinRoom -import io.element.android.libraries.preferences.impl.store.DefaultSessionPreferencesStore -import io.element.android.libraries.push.test.notifications.FakeNotificationCleaner -import io.element.android.services.analytics.noop.NoopAnalyticsService -import io.element.android.services.toolbox.impl.strings.AndroidStringProvider -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withContext -import kotlinx.datetime.Clock -import kotlinx.datetime.TimeZone -import timber.log.Timber -import java.util.Locale - -class RoomListScreen( - context: Context, - private val matrixClient: MatrixClient, - private val coroutineDispatchers: CoroutineDispatchers = Singleton.coroutineDispatchers, -) { - private val clock = Clock.System - private val locale = Locale.getDefault() - private val dateTimeProvider = LocalDateTimeProvider(clock) { TimeZone.currentSystemDefault() } - private val dateFormatters = DateFormatters(locale, clock) { TimeZone.currentSystemDefault() } - private val sessionVerificationService = matrixClient.sessionVerificationService() - private val encryptionService = matrixClient.encryptionService() - private val stringProvider = AndroidStringProvider(context.resources) - private val featureFlagService = AlwaysEnabledFeatureFlagService() - private val roomListRoomSummaryFactory = RoomListRoomSummaryFactory( - lastMessageTimestampFormatter = DefaultLastMessageTimestampFormatter( - localDateTimeProvider = dateTimeProvider, - dateFormatters = dateFormatters - ), - roomLastMessageFormatter = DefaultRoomLastMessageFormatter( - sp = stringProvider, - roomMembershipContentFormatter = RoomMembershipContentFormatter( - matrixClient = matrixClient, - sp = stringProvider - ), - profileChangeContentFormatter = ProfileChangeContentFormatter(stringProvider), - stateContentFormatter = StateContentFormatter(stringProvider), - permalinkParser = OnlyFallbackPermalinkParser(), - ), - ) - private val presenter = RoomListPresenter( - client = matrixClient, - networkMonitor = DefaultNetworkMonitor(context, Singleton.appScope), - snackbarDispatcher = SnackbarDispatcher(), - leaveRoomPresenter = LeaveRoomPresenter(matrixClient, RoomMembershipObserver(), coroutineDispatchers), - roomListDataSource = RoomListDataSource( - roomListService = matrixClient.roomListService, - roomListRoomSummaryFactory = roomListRoomSummaryFactory, - coroutineDispatchers = coroutineDispatchers, - notificationSettingsService = matrixClient.notificationSettingsService(), - appScope = Singleton.appScope, - dateTimeObserver = DefaultDateTimeObserver(context), - ), - indicatorService = DefaultIndicatorService( - sessionVerificationService = sessionVerificationService, - encryptionService = encryptionService, - ), - featureFlagService = featureFlagService, - searchPresenter = RoomListSearchPresenter( - RoomListSearchDataSource( - roomListService = matrixClient.roomListService, - roomSummaryFactory = roomListRoomSummaryFactory, - coroutineDispatchers = coroutineDispatchers, - ), - featureFlagService = featureFlagService, - ), - sessionPreferencesStore = DefaultSessionPreferencesStore( - context = context, - sessionId = matrixClient.sessionId, - sessionCoroutineScope = Singleton.appScope - ), - filtersPresenter = RoomListFiltersPresenter( - roomListService = matrixClient.roomListService, - filterSelectionStrategy = DefaultFilterSelectionStrategy(), - ), - acceptDeclineInvitePresenter = AcceptDeclineInvitePresenter( - client = matrixClient, - joinRoom = DefaultJoinRoom(matrixClient, NoopAnalyticsService()), - notificationCleaner = FakeNotificationCleaner(), - ), - analyticsService = NoopAnalyticsService(), - fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() }, - notificationCleaner = FakeNotificationCleaner(), - logoutPresenter = DirectLogoutPresenter(matrixClient, encryptionService), - ) - - @Composable - fun Content(modifier: Modifier = Modifier) { - fun onRoomClick(roomId: RoomId) { - Singleton.appScope.launch { - withContext(coroutineDispatchers.io) { - matrixClient.getRoom(roomId)!!.use { room -> - room.liveTimeline.paginate(Timeline.PaginationDirection.BACKWARDS) - } - } - } - } - - val state = presenter.present() - RoomListView( - state = state, - onRoomClick = ::onRoomClick, - onSettingsClick = {}, - onSetUpRecoveryClick = {}, - onConfirmRecoveryKeyClick = {}, - onCreateRoomClick = {}, - onRoomSettingsClick = {}, - onMenuActionClick = {}, - onRoomDirectorySearchClick = {}, - modifier = modifier, - acceptDeclineInviteView = { - AcceptDeclineInviteView(state = state.acceptDeclineInviteState, onAcceptInvite = {}, onDeclineInvite = {}) - }, - onMigrateToNativeSlidingSyncClick = {}, - ) - - DisposableEffect(Unit) { - Timber.w("Start sync!") - runBlocking { - matrixClient.syncService().startSync() - } - onDispose { - Timber.w("Stop sync!") - runBlocking { - matrixClient.syncService().stopSync() - } - } - } - } -} diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt deleted file mode 100644 index 7b33dda53b..0000000000 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/Singleton.kt +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2023, 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.samples.minimal - -import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.core.meta.BuildMeta -import io.element.android.libraries.core.meta.BuildType -import io.element.android.libraries.matrix.api.tracing.TracingConfiguration -import io.element.android.libraries.matrix.api.tracing.TracingFilterConfigurations -import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration -import io.element.android.libraries.matrix.impl.tracing.RustTracingService -import kotlinx.coroutines.CoroutineName -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.plus - -object Singleton { - val buildMeta = BuildMeta( - isDebuggable = true, - buildType = BuildType.DEBUG, - applicationName = "EAX-Minimal", - productionApplicationName = "EAX-Minimal", - desktopApplicationName = "EAX-Minimal-Desktop", - applicationId = "io.element.android.samples.minimal", - isEnterpriseBuild = false, - lowPrivacyLoggingEnabled = false, - versionName = "0.1.0", - versionCode = 1, - gitRevision = "", - gitBranchName = "", - flavorDescription = "NA", - flavorShortDescription = "NA", - ) - - init { - val tracingConfiguration = TracingConfiguration( - filterConfiguration = TracingFilterConfigurations.debug, - writesToLogcat = true, - writesToFilesConfiguration = WriteToFilesConfiguration.Disabled - ) - RustTracingService(buildMeta).setupTracing(tracingConfiguration) - } - - val appScope = MainScope() + CoroutineName("Minimal Scope") - val coroutineDispatchers = CoroutineDispatchers( - io = Dispatchers.IO, - computation = Dispatchers.Default, - main = Dispatchers.Main, - ) -} diff --git a/samples/minimal/src/main/res/mipmap-hdpi/ic_launcher.webp b/samples/minimal/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78ecd372343283f4157dcfd918ec5165bb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1404 zcmV-?1%vuhNk&F=1pok7MM6+kP&il$0000G0000-002h-06|PpNX!5L00Dqw+t%{r zzW2vH!KF=w&cMnnN@{whkTw+#mAh0SV?YL=)3MimFYCWp#fpdtz~8$hD5VPuQgtcN zXl<@<#Cme5f5yr2h%@8TWh?)bSK`O z^Z@d={gn7J{iyxL_y_%J|L>ep{dUxUP8a{byupH&!UNR*OutO~0{*T4q5R6@ApLF! z5{w?Z150gC7#>(VHFJZ-^6O@PYp{t!jH(_Z*nzTK4 zkc{fLE4Q3|mA2`CWQ3{8;gxGizgM!zccbdQoOLZc8hThi-IhN90RFT|zlxh3Ty&VG z?Fe{#9RrRnxzsu|Lg2ddugg7k%>0JeD+{XZ7>Z~{=|M+sh1MF7~ zz>To~`~LVQe1nNoR-gEzkpe{Ak^7{{ZBk2i_<+`Bq<^GB!RYG+z)h;Y3+<{zlMUYd zrd*W4w&jZ0%kBuDZ1EW&KLpyR7r2=}fF2%0VwHM4pUs}ZI2egi#DRMYZPek*^H9YK zay4Iy3WXFG(F14xYsoDA|KXgGc5%2DhmQ1gFCkrgHBm!lXG8I5h*uf{rn48Z!_@ z4Bk6TJAB2CKYqPjiX&mWoW>OPFGd$wqroa($ne7EUK;#3VYkXaew%Kh^3OrMhtjYN?XEoY`tRPQsAkH-DSL^QqyN0>^ zmC>{#F14jz4GeW{pJoRpLFa_*GI{?T93^rX7SPQgT@LbLqpNA}<@2wH;q493)G=1Y z#-sCiRNX~qf3KgiFzB3I>4Z%AfS(3$`-aMIBU+6?gbgDb!)L~A)je+;fR0jWLL-Fu z4)P{c7{B4Hp91&%??2$v9iRSFnuckHUm}or9seH6 z>%NbT+5*@L5(I9j@06@(!{ZI?U0=pKn8uwIg&L{JV14+8s2hnvbRrU|hZCd}IJu7*;;ECgO%8_*W Kmw_-CKmY()leWbG diff --git a/samples/minimal/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/samples/minimal/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d1ba5cf3ee31b3ecc1ced89044a1f3b7a9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2898 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 diff --git a/samples/minimal/src/main/res/mipmap-mdpi/ic_launcher.webp b/samples/minimal/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d64e58ba64d180ce43ee13bf9a17835fbca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 982 zcmV;{11bDcNk&G_0{{S5MM6+kP&il$0000G0000l001ul06|PpNU8t;00Dqo+t#w^ z^1csucXz7-Qrhzl9HuHB%l>&>1tG2^vb*E&k^T3$FG1eQZ51g$uv4V+kI`0<^1Z@N zk?Jjh$olyC%l>)Xq;7!>{iBj&BjJ`P&$fsCfpve_epJOBkTF?nu-B7D!hO=2ZR}

C%4 zc_9eOXvPbC4kzU8YowIA8cW~Uv|eB&yYwAObSwL2vY~UYI7NXPvf3b+c^?wcs~_t{ ze_m66-0)^{JdOMKPwjpQ@Sna!*?$wTZ~su*tNv7o!gXT!GRgivP}ec?5>l1!7<(rT zds|8x(qGc673zrvYIz;J23FG{9nHMnAuP}NpAED^laz3mAN1sy+NXK)!6v1FxQ;lh zOBLA>$~P3r4b*NcqR;y6pwyhZ3_PiDb|%n1gGjl3ZU}ujInlP{eks-#oA6>rh&g+!f`hv#_%JrgYPu z(U^&XLW^QX7F9Z*SRPpQl{B%x)_AMp^}_v~?j7 zapvHMKxSf*Mtyx8I}-<*UGn3)oHd(nn=)BZ`d$lDBwq_GL($_TPaS{UeevT(AJ`p0 z9%+hQb6z)U9qjbuXjg|dExCLjpS8$VKQ55VsIC%@{N5t{NsW)=hNGI`J=x97_kbz@ E0Of=7!TQj4N+cqN`nQhxvX7dAV-`K|Ub$-q+H-5I?Tx0g9jWxd@A|?POE8`3b8fO$T))xP* z(X?&brZw({`)WU&rdAs1iTa0x6F@PIxJ&&L|dpySV!ID|iUhjCcKz(@mE z!x@~W#3H<)4Ae(4eQJRk`Iz3<1)6^m)0b_4_TRZ+cz#eD3f8V;2r-1fE!F}W zEi0MEkTTx}8i1{`l_6vo0(Vuh0HD$I4SjZ=?^?k82R51bC)2D_{y8mi_?X^=U?2|F{Vr7s!k(AZC$O#ZMyavHhlQ7 zUR~QXuH~#o#>(b$u4?s~HLF*3IcF7023AlwAYudn0FV~|odGH^05AYPEfR)8p`i{n zwg3zPVp{+wOsxKc>)(pMupKF!Y2HoUqQ3|Yu|8lwR=?5zZuhG6J?H`bSNk_wPoM{u zSL{c@pY7+c2kck>`^q1^^gR0QB7Y?KUD{vz-uVX~;V-rW)PDcI)$_UjgVV?S?=oLR zf4}zz{#*R_{LkiJ#0RdQLNC^2Vp%JPEUvG9ra2BVZ92(p9h7Ka@!yf9(lj#}>+|u* z;^_?KWdzkM`6gqPo9;;r6&JEa)}R3X{(CWv?NvgLeOTq$cZXqf7|sPImi-7cS8DCN zGf;DVt3Am`>hH3{4-WzH43Ftx)SofNe^-#|0HdCo<+8Qs!}TZP{HH8~z5n`ExcHuT zDL1m&|DVpIy=xsLO>8k92HcmfSKhflQ0H~9=^-{#!I1g(;+44xw~=* zxvNz35vfsQE)@)Zsp*6_GjYD};Squ83<_?^SbALb{a`j<0Gn%6JY!zhp=Fg}Ga2|8 z52e1WU%^L1}15Ex0fF$e@eCT(()_P zvV?CA%#Sy08_U6VPt4EtmVQraWJX` zh=N|WQ>LgrvF~R&qOfB$!%D3cGv?;Xh_z$z7k&s4N)$WYf*k=|*jCEkO19{h_(%W4 zPuOqbCw`SeAX*R}UUsbVsgtuG?xs(#Ikx9`JZoQFz0n*7ZG@Fv@kZk`gzO$HoA9kN z8U5{-yY zvV{`&WKU2$mZeoBmiJrEdzUZAv1sRxpePdg1)F*X^Y)zp^Y*R;;z~vOv-z&)&G)JQ{m!C9cmziu1^nHA z`#`0c>@PnQ9CJKgC5NjJD8HM3|KC(g5nnCq$n0Gsu_DXk36@ql%npEye|?%RmG)

FJ$wK}0tWNB{uH;AM~i diff --git a/samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher.webp b/samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a3070fe34c611c42c0d3ad3013a0dce358be0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1900 zcmV-y2b1_xNk&Fw2LJ$9MM6+kP&il$0000G0001A003VA06|PpNH75a00DqwTbm-~ zullQTcXxO9ki!OCRx^i?oR|n!<8G0=kI^!JSjFi-LL*`V;ET0H2IXfU0*i>o6o6Gy zRq6Ap5(_{XLdXcL-MzlN`ugSdZY_`jXhcENAu)N_0?GhF))9R;E`!bo9p?g?SRgw_ zEXHhFG$0{qYOqhdX<(wE4N@es3VIo$%il%6xP9gjiBri+2pI6aY4 zJbgh-Ud|V%3O!IcHKQx1FQH(_*TK;1>FQWbt^$K1zNn^cczkBs=QHCYZ8b&l!UV{K z{L0$KCf_&KR^}&2Fe|L&?1I7~pBENnCtCuH3sjcx6$c zwqkNkru);ie``q+_QI;IYLD9OV0ZxkuyBz|5<$1BH|vtey$> z5oto4=l-R-Aaq`Dk0}o9N0VrkqW_#;!u{!bJLDq%0092{Ghe=F;(kn} z+sQ@1=UlX30+2nWjkL$B^b!H2^QYO@iFc0{(-~yXj2TWz?VG{v`Jg zg}WyYnwGgn>{HFaG7E~pt=)sOO}*yd(UU-D(E&x{xKEl6OcU?pl)K%#U$dn1mDF19 zSw@l8G!GNFB3c3VVK0?uyqN&utT-D5%NM4g-3@Sii9tSXKtwce~uF zS&Jn746EW^wV~8zdQ1XC28~kXu8+Yo9p!<8h&(Q({J*4DBglPdpe4M_mD8AguZFn~ ztiuO~{6Bx?SfO~_ZV(GIboeR9~hAym{{fV|VM=77MxDrbW6`ujX z<3HF(>Zr;#*uCvC*bpoSr~C$h?_%nXps@A)=l_;({Fo#6Y1+Zv`!T5HB+)#^-Ud_; zBwftPN=d8Vx)*O1Mj+0oO=mZ+NVH*ptNDC-&zZ7Hwho6UQ#l-yNvc0Cm+2$$6YUk2D2t#vdZX-u3>-Be1u9gtTBiMB^xwWQ_rgvGpZ6(C@e23c!^K=>ai-Rqu zhqT`ZQof;9Bu!AD(i^PCbYV%yha9zuoKMp`U^z;3!+&d@Hud&_iy!O-$b9ZLcSRh? z)R|826w}TU!J#X6P%@Zh=La$I6zXa#h!B;{qfug}O%z@K{EZECu6zl)7CiNi%xti0 zB{OKfAj83~iJvmpTU|&q1^?^cIMn2RQ?jeSB95l}{DrEPTW{_gmU_pqTc)h@4T>~& zluq3)GM=xa(#^VU5}@FNqpc$?#SbVsX!~RH*5p0p@w z;~v{QMX0^bFT1!cXGM8K9FP+=9~-d~#TK#ZE{4umGT=;dfvWi?rYj;^l_Zxywze`W z^Cr{55U@*BalS}K%Czii_80e0#0#Zkhlij4-~I@}`-JFJ7$5{>LnoJSs??J8kWVl6|8A}RCGAu9^rAsfCE=2}tHwl93t0C?#+jMpvr7O3`2=tr{Hg$=HlnjVG^ewm|Js0J*kfPa6*GhtB>`fN!m#9J(sU!?(OSfzY*zS(FJ<-Vb zfAIg+`U)YaXv#sY(c--|X zEB+TVyZ%Ie4L$gi#Fc++`h6%vzsS$pjz9aLt+ZL(g;n$Dzy5=m=_TV(3H8^C{r0xd zp#a%}ht55dOq?yhwYPrtp-m1xXp;4X;)NhxxUpgP%XTLmO zcjaFva^}dP3$&sfFTIR_jC=2pHh9kpI@2(6V*GQo7Ws)`j)hd+tr@P~gR*2gO@+1? zG<`_tB+LJuF|SZ9tIec;h%}}6WClT`L>HSW?E{Hp1h^+mlbf_$9zA>!ug>NALJsO{ mU%z=YwVD?}XMya)Bp;vlyE5&E_6!fzx9pwrdz474!~g(M6R?N? diff --git a/samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/samples/minimal/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a6956b3acdc11f40ce2bb3f6efbd845cc243f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3918 zcmV-U53%r4Nk&FS4*&pHMM6+kP&il$0000G0001A003VA06|PpNSy@$00HoY|G(*G z+qV7x14$dSO^Re!iqt-AAIE9iwr$(CZQJL$blA4B`>;C3fBY6Q8_YSjb2%a=fc}4E zrSzssacq<^nmW|Rs93PJni30R<8w<(bK_$LO4L?!_OxLl$}K$MUEllnMK|rg=f3;y z*?;3j|Nh>)p0JQ3A~rf(MibH2r+)3cyV1qF&;8m{w-S*y+0mM){KTK^M5}ksc`qX3 zy>rf^b>~l>SSHds8(I@hz3&PD@LmEs4&prkT=BjsBCXTMhN$_)+kvnl0bLKW5rEsj z*d#KXGDB4P&>etx0X+`R19yC=LS)j!mgs5M0L~+o-T~Jl!p!AJxnGAhV%~rhYUL4hlWhgES3Kb5oA&X z{}?3OBSS-{!v$nCIGj->(-TAG)8LR{htr41^gxsT8yqt2@DEG6Yl`Uma3Nd4;YUoW zTbkYl3CMU5ypMF3EIkYmWL|*BknM`0+Kq6CpvO(y$#j94e+q{vI{Zp8cV_6RK!`&C zob$*5Q|$IZ09dW=L!V zw@#2wviu|<#3lgGE8GEhcx+zBt`} zOwP8j9X%^f7i_bth4PiJ$LYtFJSCN$3xwDN;8mr*B;CJwBP2G0TMq0uNt7S^DO_wE zepk!Wrn#Z#03j{`c*Rf~y3o7?J}w?tEELRUR2cgxB*Y{LzA#pxHgf}q?u5idu>077 zd^=p)`nA}6e`|@`p?u}YU66PP_MA}Zqqe!c{nK&z%Jwq1N4e_q<#4g^xaz=ao;u|6 zwpRcW2Lax=ZGbx=Q*HhlJ`Ns#Y*r0*%!T?P*TTiX;rb)$CGLz=rSUum$)3Qyv{BL2 zO*=OI2|%(Yz~`pNEOnLp>+?T@glq-DujlIp?hdJeZ7ctP4_OKx|5@EOps3rr(pWzg zK4d3&oN-X2qN(d_MkfwB4I)_)!I_6nj2iA9u^pQ{;GckGLxBGrJUM2Wdda!k)Y>lq zmjws>dVQ*vW9lvEMkiN3wE-__6OWD0txS&Qn0n22cyj4Q*8(nG4!G{6OOwNvsrPIL zCl-$W9UwkEUVuLwyD%|inbOF*xMODZ4VMEVAq_zUxZ+K#Gdqf!DW$5f)?7UNOFMz! zrB~tuu=6X2FE(p^iqgxr+?ZK;=yz`e;C$#_@D9Lj-+TDVOrva>(#*PVbaHO>A)mhl z07OJWCqYC60518$!&c`eNBcBW%GnfaQ*$eazV^2_AW?j)h;J1nUjN(I9=0+!RVx~% z3@Tf!P0TE+98jA?WceK-}A1% zW!K)lyKcGqy#M~})315-A#2NXQ`?6NR#Apo=S!oF=JfpX>iR*49ec{7AN$xxpK{D$ z2d%Fz&rdfSqourN$~Y^NFIMV1CZ?J*bMx~H3k&meGtH@q9ra2vZxmA$S(#jaaj-g4 ztJmxG+DLV<*q<|sDXPp$X>E)#S}Vm&sRaO5P&goh2><}FEdZSXDqsL$06sAkh(e+v zAsBhKSRexgwg6tIy~GFJzaTxXD(}|+0eOwFDA%rn`X;MVwDHT9=4=g%OaJ9s%3b9>9EUTnnp0t;2Zpa{*>mk~hZqItE_!dQ zOtC>8`$l|mV43Jbudf0N6&&X;{=z}Zi}d1`2qmJ}i|0*GsulD3>GgQXHN)pkR6sf1 z?5ZU%&xtL}oH;YiAA)d*^Ndw2T$+Mjuzyzz@-SM`9df7LqTxLuIwC~S0092~+=qYv z@*ja;?Wt!T!{U?c*Z0YtGe)XbI&y-?B&G2$`JDM)(dIV9G`Sc#6?sI60de6kv+)Qb zUW~2|WjvJq3TA8`0+sWA3zRhY9a~ow)O~&StBkG2{*{TGiY~S8ep{V&Vo2l<6LWsu z^#p0-v*t2?3&aA1)ozu|%efSR=XnpX$lvTeRdKlvM!@|pM5p2w3u-6 zU>}t2xiYLS+{|%C65AzX+23Mtlq?BS&YdYcYsVjoiE&rT>;Necn6l^K)T^lmE`5u{ zm1i+-a-gc;Z&v-{;8r)z6NYfBUv+=_L}ef}qa9FX01)+Aaf+;xj(mL6|JUzGJR1|fnanb%?BPPIp>SCjP|8qE5qJ{=n5ZGw?81z3(k;pzH%1CtlX50{E7h)$h{qGKfzC`e2o`*IqA#tjA z`Fz&^%$b9F*N`)U-#6>a)Z`55`$Dd0cfcs0$d13^ONrdCu9xcv_=n#WQo8stcz3jP9|2EvdI-RhJM3%Q%oM&!OlShM|0 z?gz?wHZSnm45njLtsz8PVT1S&jAlbKg5kVam$p16=EK@Sj4EP0OtH zmJDmdc^v)x>56Qg_wmYHz6h)>kl_h$>0@J!ypv%APmjZTAQVLy6Fu50RGY&JAVNhx zrF_qG6`x9MkT;1SFWo$)l{M$;3qUDn9JwE}z zRl#E_bDRJFii61kPgBybIgp8dNW!Cc1b*^YYk-#oWLJvtM_v^hQx~9?8LD4VFFxBF z3MlrsSC%f9Oupn*ctPL0U1fwfX?`tRhPD{PSLFPQOmIt$mDy0SgpNVvHS+f#Do>h1Gn?LZU9(KaN>Q_=Y*_T zvtD7%_u^^+{g`0VGzg(VZrpVQ6Ub5M=tI_p7T93R8@3Zulu3|#{iNcu!oiHxZ4Rf*( zfmiN$$ru(*_Zqn=`Gq#OuHRTSwp7uH_SokR&|)RuW5yo=Z|_4?qU-JU+tpt>!B&Is z@N(=SG;bpVc;AO@zbmMM zScqq1)b-ZQIrs={oD}|?6y{$HNB1U0^LsBh8JI&3!GBZxOXI<}&5-$lgkAaYqhOTb z?2vEnZ$-kk;*M_17(upJF3%+iH*s0-r{vttXVB2OUwI1s^+G(Ft(U8gYFXC}#P&E^ z>T@C^tS`Z7{6HT4_nF~n>JlZtk5&qDBl6r|^kzQYe`wq!C)n@$c>WOPA61NDFj<<6 zGW71NMMhwAl!U-yqrq2xrSFqRCI8acw7?}3j;ynxo*-b7Co;g5r%^j=H@9({PXXBf z@r>U>>N;E)81wx`B4f%{PB~MHka_);%kBCb(d|Jy5!MqJ%2p`t&@L)4$T2j&-WHvG zv3(uyA_gwqNu(k?jQTtv3dgPKRZoH8prxe7>pQBW5L&dpumS&5Ld2?(sCpJjvc4L5 zEnh&?91WVm)ZdTj=fjJ$pPDdgAttLXuke+?KdKxu*;kTC(r!tQk6;gxj4h%FdHAt(^M3YvYj(!tOeN)+Hvj6+< zzyJRG?^lZfWuR#t!tUKP&(?%3v&Zd$R2YN>lB(Lq`OInY48%4%yTv2 zYe1{G`3)(PDEio5Y@-I5tUf`c%%OCJMtSW56g3iEg%3`$7XSJJHyA z<|7&N)5Xrlgv~%BO24eFd;Hd;uiK%D`EdK|quUeRZDqbh9l)%j%J#0lfrZumvA<_w zu&=AVvdChf6}eqh(bUz`(`Ue*p01{fBAcTgKyDYLs_I+YyJEk+rM@avU~>fB$n)HS zM7pfJydu`i%gfS<{PF94kZDv$t>06sAkheDzu40NJ$5CMW%n^Lls?8^p^QGWURbKu3ZduZQZ((s2? zzE`}<{;Zt7<$C|9R8A~DJ~@%x>TfP zF>TX8)@v|t)q4GjRt<}5s6hLHwRel7>V@&r-O|Av(yh;Q1A{E>Ir>p+%dHD|=l+lT zpr(Dg&>#Nu=!)6bCLr-ZS%|;h)Ij$+e@r8_{qO19QvDe=&1tmpY*0lcA^Cc-#{9fQ z<~$*<&P$Q<_jy#<$40PMofM7aQ}C=jphI`4kLg}Z7CIN#26D{-4v-_CA-LiE@(%{y!BzsU%gG`Q?sjLUf%qFSl0y)2#ae*+EI>s|i`d^V$Dn)qmzqRq6VJRY|{4ujsIU%#bnqU6MR&-1I_43=|5(6Jr;Jvert) zE?S|Tmn}Tv<-??sxV5@9t}3D=>YZ0JrQe$CO~|EY=Lj9RM&4svQHPQL6%pV5fPFiH zfXDx;l@~et{*{U*#c#Dvzu)|znDO7$#CRx)Z&yp-}SrD{&|(MQtfUz~n35@RLfUy=aqrhCX0M}J_r5QsK~NmRCR|Nm&L z41UdsLjWxSUlL41r^0K&nCCK>fdR-!MYjFg(z9_mF^C|#ZQw?`)f6uVzF^`bRnVY& zo}@M06J&_+>w9@jpaO4snmU;0t-(zYW1qVBHtuD!d?%?AtN7Plp><-1Y8Rqb20ZaP zTCgn*-Sri4Q8Xn>=gNaWQ57%!D35UkA@ksOlPB*Dvw}t02ENAqw|kFhn%ZyyW%+t{ zNdM!uqEM^;2}f+tECHbwLmH*!nZVrb$-az%t50Y2pg(HqhvY-^-lb}>^6l{$jOI6} zo_kBzj%8aX|6H5M0Y<)7pzz_wLkIpRm!;PzY)9+24wk2&TT{w--phDGDCOz{cN_ca zpnm7`$oDy=HX%0i-`769*0M6(e5j-?(?24%)<)&46y0e&6@HCDZAm9W6Ib#Y#BF6- z=30crHGg+RRTe%VBC>T00OV6F+gQDAK38Ne3N9bm|62tPccBJi)5{B z4zc^Db72XiBd}v$CF|yU{Z=M|DZ%-(XarYNclODlb1Kz1_EKLy(NSLCN`eUl(rBCL zT*jx@wNvze0|TSqgE(QArOZU)_?qH(sj#TwzElLs9q)(0u!_P|R%Cy_0JFQxgGV>1 zz4?_uq<8_gM0`c*Hh|;UMz~vrg1gQXp{ufg`hM_qU;U>+zmvc5blCLSq@PrEBSGR# z&8=2Z4uXN`F3p73ueD1l{s{k$WipAvSh5W7ABe?4)t;r@V?y`bNB5FvBuE|0VRTb< zM1Hn^?DSsJY+sX@T5xW=#>T9VEV|?<(=6|ge$X6Sb05!LFdjDcoq*gM(Zq=t;_)Le&jyt(&9jzR73noru`a# zN*<`KwGa^gZU3-)MSLF0aFag#f0<>E(bYTeHmtdbns#|I)-$)mJ`q9ctQ8g0=ET?| zdO}eZ*b_p>ygRTtR^5Ggdam=Zb5wmd{}np+Jn1d_=M`~P=M67jj})fH4ztb5yQqQW z^C|C&^LHAK-u+ooIK)yM)QM?t;|<{P;;{`p=BclzAN#JzL4jCwXkQB1Dy{=^KR`=~ zTrr)y7eiYBzSNs_DvO=4A6#EgGS-zY%Vi)N*Yb`U;6o}KR}dq{r9pT5wqZ@3NOE8- z9-(}D|Nc5732CSYQbL)!gPQ#RbD8BhK3dl{sUuPvei0tkvnJBxDEAYTesU8H$)g(Plra{VH(v3u^CO1~(+ zU0O7#)jaS4{NcwA+LuSm&VBcX2#Im3xg)W}ySNw%->orn1taZ&+d)}8gJTqA!u|5P z{yv?zol_3|(1(%M(EVU=cp?L`{Pi|ixk{U)*guFML3P!OSlz;zGA#T+E@8@cgQ_mv1o7RSU=Zo_82F?&&2r;WE z@wk}JHYEZ9nYUc(Vv~iTCa3u8e4q(yq<29VoNbKk|`mq%I6u)My=gPIDuUb&lzf4`MEA9^g8u z)vp8|$$HE9m_BTV?lOosIGa4jud=jIbw)O2eCMfyw2*S8?hjWw^nqws$O*M$3I1)x zR0PWFb3$ySOcGTe1dz%N0l;RPc`x%05FtT^f^j{YCP}*Q=lvp4$ZXrTZQHhO+w%wJn3c8j%+5C3UAFD&%8dBl_qi9D5g8fry}6Ev z2_Q~)5^N$!IU`BPh1O|=BxQ#*C5*}`lluC515$lxc-vNC)IgW=K|=z7o%cWFpndn= zX}f{`!VK02_kU+Q5a3m37J;c} zTzbxteE{GNf?yLt5X=Bzc-mio^Up0nunMCgp*ZJ;%MJvPM3QK)BryP(_v@ei4UvHr z6+sbCifQaOkL6-;5fL8$W($zZ_;CZp305C;~$hhRquZr-r)jjd1z z31%ZK{-(`P#|Um_Sivn@p$-vz46uqT>QG0B1w9znfS9A8PB2LaHdzA|_)yjXVR*l{ zkcu3@vEf7bxH0nkh`q?8FmoO_Ucui*>_a~P?qQrlZ9@+D7%MTpSnztpylXrt5!-k8_QPB?YL8Kx_On8WD zgT+111d(Op$^$&KLAN5+@?>f7F4~wFi(8TL8+szgVmcMDTp5l&k6~=rA{Dt}!gb^r zSWY<)M7D|Z2P0cEodj6E42PV>&>DFmQpgt)E-|#sSUU@uKed+F680H@<;-x{p|nuH4!_mn85rx>wz;0mPi2ZkL#k6;sznu?cXh!T0S>{w6 zL^gvR05NY64l*<+_L>On$rjx9!US;l;LX6@z}yi#2XHh)F@Oo+l)h%fq$v}DNmF2> zfs^_t0)3N-W<9-N?uedVv{)-J0W5mh#29QM5R5h&KuiRM=0Zvnf#lF=K#WlCgc#9c zS;qvh(P$!_a8JwyhI^ZJV2k+B6Z^64?w|1?5gyo6y{}923CRZfYVe1#?F% z7h2SUiNO3;T#JUOyovSs@@C1GtwipycA=*x5{BpIZ_#GCMuV8XK=x;qCNy{d7?wA~ zC+=vjls;ci&zW=6$H~4^K%v{p}Ab?U%C6Z4p%eC<3ExqU$XR<}LLF67A$Sr20DR_pJ3yeBa~ z^sw{V0FI5;UpwXsScYuhbqGQ`YQ25;6p6W^+tgL&;Ml;>S3CGpSZ>VrTn0m1$y$HU z&65)I!c?oREz};c=nLCliriqQX->4uivHTgd${GqeAlf*!P^B|jkU|*IdNP(&6C>4 zqOW$)Nw9nvjy^&`?E|gotDV{JmJ9Q~vuhy<`^C4XIUDt|j4o6rK^e8_(=YqC zuaR6TRVf@tUFHB079o4MBIh{M~4>WwnGgesQH*3?w(RA%hCZ*7)b!aNV=yOQ%o_Y=Lt0Sl*(9^jfRnC210Om$=y>*o|3z} zAR&vAdrB#mWoaB0fJSw9xw|Am$fzK>rx-~R#7IFSAwdu_EI|SRfB*yl0w8oX09H^q zAjl2?0I)v*odGJ40FVGaF&2qJq9Gv`>V>2r0|c`GX8h>CX8eHcOy>S0@<;M3<_6UM z7yCEpug5NZL!H_0>Hg_HasQGxR`rY&Z{geOy?N92Z z{lER^um|$*?*G63*njwc(R?NT)Bei*3jVzR>FWUDb^gKhtL4A=kE_1p-%Fo2`!8M} z(0AjuCiS;G{?*^1tB-uY%=)SRx&D)pK4u@>f6@KPe3}2j_har$>HqzH;UCR^ssFD0 z7h+VLO4o@_Yt>>AeaZKUxqyvxWCAjKB>qjQ30UA)#w z&=RmdwlT`7a8J8Yae=7*c8XL|{@%wA8uvCqfsNX^?UZsS>wX}QD{K}ad4y~iO*p%4 z_cS{u7Ek%?WV6em2(U9#d8(&JDirb^u~7wK4+xP$iiI6IlD|a&S)6o=kG;59N|>K1 zn(0mUqbG3YIY7dQd+*4~)`!S9m7H6HP6YcKHhBc#b%1L}VIisp%;TckEkcu0>lo@u995$<*Em;XNodjTiCdC%R+TX|_ZR#|1`RR|`^@Teh zl#w@8fI1FTx2Dy+{blUT{`^kY*V-AZUd?ZZqCS4gW(kY5?retkLbF=>p=59Nl|=sf zo1Pc|{{N4>5nt#627ylGF`3n>X%`w%bw-Y~zWM_{Si$dc82|=YhISal{N7OY?O`C4 zD|qb}6nLWJ`hUyL+E>-;ricg9J@ZNYP(x(Sct&OI$Y!QWr*=^VN;G3#i>^1n4e#Je zOVhbFbLpXVu*16enDM+ic;97@R~u&kh__kgP#!R`*rQEnA+_dLkNP~L`0alC|J;c; zeiK=s8;BsLE)KbG3BD&Br@(Ha@SBT&$?xX`=$;eeel=|R_dIr6-Ro?=HEjnsJ_b`1 zK6Yg^-6;^2aW!xeTK)A~3Rm|L^FCHB_I>jIju7ZGo&N_1*QHkxH2!!%@o4iZ?vntS;&zJdPe1dH#04YD93A44o-MpfD zP{rn_aq>U%RDvC2+bp;xPlsOzauIi3*Lf42`jVKKZCRuKdYhi>FDuL2l=v{$BCN#Q6796s%r-AG$Q^t(3c@ zD?w0UhYr11@feiyl9kY_@H8~|xlmO<8PfQmj1!$@WieW@VxR@Psxfe-v9WCi1+f>F4VL?0O~K7T?m4-u|pSkBpUJZZe*16_wAp zSYZ@;k`3;W3UHKUWc8QeI}0jH5Ly=cGWQPw(Kr2fm=-5L(d`lcXofy8tJY3@Tuadz zYWXR{mW7XT!RF#RVCe%}=tM*O6!AD3^(!8un~opNI%Uko7$5t@<8+?; zTxDys(MyyGsUjtSu9$+|_-t!U3fVb1dkK?l`17<+jfl=hrBHnDSV>^R1=TnQeyqbW z>ov#l%!1|S!1>8UUxIdhQq`_klcHVx0{?#>K3#$4GlXncwldt!g17TcvKq-jo_996 z>oA=tH9CqRl6Yw?Uc`am!V?lHJbizOJaVaScf1UP5e7Dbgabq=b!B~T&_F6?ooU>w%x0A zH~&MHJ=q`fCH{U<7MDXE4SD32cDZA)WJeWkllJ`UspWaS#eDe^kg^oU_A14UE9zG-a^g{xaXf$})Wik>gT zl#dkzGr(;h0JZDuFn(+k8wNq?PZ5grQ<+sM?wBGt@JnH6v0#or-5wBQWKU~(S_> zkE!tc*ZJ1Y&*p(xX84POb3cClRMd!^qJ#CAZfIepEj-<`VURS_yCz0(?*Ixcj4 z-!zV1_QZhpm=0<;*(nm+F>T=)o?ep@CK5I%g^VAA+RB25ab?7)A~z~egru=I1S|@v zH7tXV!0wmGS^qj#e+MY;C5eUjEAp$Y?LDkS^QPZ}8WN85?r$u<-Epi;yZ1|J2J`se z$D6DpH~2F=eI0B&=UFAUnJvZAmClJlK)sutJ?M>xpZiWV&0=G4MZP+x+p>EX=HbCz zxls%Mw?*u^;LbHWIWCyq+yi)`GmFn9J112CZda_u@YIP%i;srFg_paU02Ifij*7}l z&CF-(3|>*a|+vbNR`^RP=9G?ymEJ0Z~)d&c*UE$UMepZ zcITr{0WqhxkjUnM15js_gW=e3Uh|y6ZReaXHIz-=p`x5VvB&rH9y>Amv@^WmXFEw) zQXYrk3feir=a{jMQ+wDIkkFnZ$k{sJakHn*?u za%4b!00ev8NVLM1TY=cl?KB&55BY_MU-sg?c>=Dbz_W{(Z~c?HJi*XpYL)C6Bd8WH zt+v-#0&o~@t4qESi*)+eW%@VD0|o^yF)n0hME$UtXF$*Lvh}7sso{`|pn*JDIy5^Fm3s$5*zEE=?u5<=l8FJc3r%+H} zdfoNl2J0^~!-*mOL5o-x32|e0Im*E!yY7F7E5N)W3>+v_LBydlEx?4$RL5f2oYRD# zaR0wv(-p~wO0eLDl3K=%`{5+0Gd$ktO=W)gWlGZJ0`K z$_RNA=ckrfa;H0KA~dR^p�(p-{x$&=IACIfoAR!za)F-^da-t3#0Dycnp zwO~NVXwXCl;jE<}>%@xz|=8fIJAB?>+E{7)|4l${4ngA3G|=r z2Dyv;VVWSgZx9Wj>qUjleGl3Ei9K4>h!(lPS%8VOG>Xu0%6VDz^O=bjJmuP7>DeUv zrbI}MlHB^^d?{zv6d=@_ZD2lg1&G7UjnVN{1}9WkaM3H~btX0GtSzB+tZ^qRgWo4m z!GmimlG$=wgXCnr6j@m<1gAL46#T~5Bnm=2{^@>|t&`9mkEPddj zAvG~@Tv~TAm2i%VW}R-g(Z0)z-Y|szHr@rk>4MAyG*Ma*7Yh#H7(!-5>DZ@8r;_dx z{prSe<>~099F8vsYd2xff7uAS%7{S)f(|@me3t2$iy&NEc7OUEchp@9A|X;;IA>8!oX+y(BKJ$EzV* znR$z;!L$s7uy@{OT~nG#B!NRraT8(X##Ho!0r_o@gg0CA-9H^;-uE&?$2$nHv_00o z%cbuUc-tCx$Uh&EZ4Nf4Zgqv)Y6>usG3>GeQnxx_Z6+PcbX-+ysbt1hQ`K1LDpOE? zrAhIZhSN9yVIAOa22gn577tbc&i3|3V8NWy&!tw##`}9*x}gtI^h1DzZRA>UuaJG) zaZ7j)dq!O}{?#8Y7~7i6fHh4{`pL?>-18|p!S75Y#^DM>-S3)vuZG+Q7l@ek zQP~#cBpWgg#mApc_sPYjpw8odQuRokmTkzcNl`^CcKB7e&;zViV;{Y{o^Y$%7i0m# z62%#1Lq!RC?}lK>%mp}T!3Xv;L*0v*>USLm``N%>w>@fwC+#T&Tx2bN4w(20JB}oU zuSa6v^kXi0xPs?pbaOHnyiqq6By1EZY9OZ^^QA>{q-Hsd&m`pbQ%8121aWG-F5xf zlZ%;B{;C>X19|`^_?dVyCq>n+41w7|!tUS!{9rHlbhX=SZO5CQ^;!Du_E7*`GiR^Q w)2!4MKjfSAeNo!9>IaV6aUZ*?W>} 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

UmY2RIYtNPVDpE$%vda+HD#3m z&VuXJ{BK&Qe+rBa7eq}Q(bq|tn(RrJAk|ztj2(i{d>nmQnM?;HF2k&9sA6up5tmjl z7lySlzMbifH17-m-Lwa_F&e7nOH?ESi3#ckR3tsM+jsck3`oG!uMS}|eAwVXv>}qxwq?QY%QJ0}r@^;fhuUA9W z*BVl>TGo&N004@xSiwDUXUvp51sVmqO3m)=B55aPwf@0=e}cN+$-BdKxY`YrT_4)0 z_d10#i44Q*rFr8MC>*)v$EJvz``(pb{e&*6k+b zsMz%($|1+8hn8c2?P(l@;Rb&CsZeYoCI3?2!LqjbwPXW3z4G$Qfj=cT5Yb%vY0(AX oeb?AaKtwrnc|$|zzw9vfvn^aJJ!zd)XFXqqy0000001=f@-~a#s diff --git a/samples/minimal/src/main/res/values-night/themes.xml b/samples/minimal/src/main/res/values-night/themes.xml deleted file mode 100644 index bcbbf3cdbd..0000000000 --- a/samples/minimal/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - -