diff --git a/.maestro/tests/account/changeServer.yaml b/.maestro/tests/account/changeServer.yaml index b56b59e2f1..1971e5a71b 100644 --- a/.maestro/tests/account/changeServer.yaml +++ b/.maestro/tests/account/changeServer.yaml @@ -9,13 +9,10 @@ appId: ${APP_ID} - tapOn: "Other" - tapOn: id: "change_server-server" -# Test server that does not support sliding sync. -- inputText: "https://kieranml.ems-support.element.dev" +- inputText: "element" - hideKeyboard -- tapOn: "kieranml.ems-support.element.dev" -- extendedWaitUntil: - visible: "This server currently doesn’t support sliding sync." - timeout: 10000 -- tapOn: "Cancel" -- back -- back +- tapOn: "element.io" +# Revert to matrix.org +- tapOn: + id: "login-change_server" +- tapOn: "matrix.org" diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 80c1a06737..d1e3447570 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -67,6 +67,7 @@ import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.MAIN_SPACE import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.sync.SyncState import io.element.android.libraries.push.api.notifications.NotificationDrawerManager import io.element.android.services.appnavstate.api.AppNavigationStateService @@ -149,6 +150,23 @@ class LoggedInFlowNode @AssistedInject constructor( } ) observeSyncStateAndNetworkStatus() + observeInvitesLoadingState() + } + + private fun observeInvitesLoadingState() { + lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + matrixClient.roomListService.invites.loadingState + .collect { inviteState -> + when (inviteState) { + is RoomList.LoadingState.Loaded -> if (inviteState.numberOfRooms == 0) { + backstack.removeLast(NavTarget.InviteList) + } + RoomList.LoadingState.NotLoaded -> Unit + } + } + } + } } @OptIn(FlowPreview::class) diff --git a/changelog.d/1449.bugfix b/changelog.d/1449.bugfix new file mode 100644 index 0000000000..adcca27f05 --- /dev/null +++ b/changelog.d/1449.bugfix @@ -0,0 +1 @@ +Adjust mention pills font weight and horizontal padding diff --git a/changelog.d/1918.misc b/changelog.d/1918.misc new file mode 100644 index 0000000000..d850c133f8 --- /dev/null +++ b/changelog.d/1918.misc @@ -0,0 +1 @@ +Add ability to see the room avatar in the media viewer. diff --git a/changelog.d/2022.feature b/changelog.d/2022.feature new file mode 100644 index 0000000000..286fde741a --- /dev/null +++ b/changelog.d/2022.feature @@ -0,0 +1 @@ +Always close the invite list screen when there is no more invite. diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt index 7957eb2200..cc9ded778b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesFlowNode.kt @@ -242,7 +242,6 @@ class MessagesFlowNode @AssistedInject constructor( backstack.push(navTarget) } is TimelineItemVideoContent -> { - val mediaSource = event.content.videoSource val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( name = event.content.body, @@ -250,13 +249,12 @@ class MessagesFlowNode @AssistedInject constructor( formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension ), - mediaSource = mediaSource, + mediaSource = event.content.videoSource, thumbnailSource = event.content.thumbnailSource, ) backstack.push(navTarget) } is TimelineItemFileContent -> { - val mediaSource = event.content.fileSource val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( name = event.content.body, @@ -264,13 +262,12 @@ class MessagesFlowNode @AssistedInject constructor( formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension ), - mediaSource = mediaSource, + mediaSource = event.content.fileSource, thumbnailSource = event.content.thumbnailSource, ) backstack.push(navTarget) } is TimelineItemAudioContent -> { - val mediaSource = event.content.mediaSource val navTarget = NavTarget.MediaViewer( mediaInfo = MediaInfo( name = event.content.body, @@ -278,7 +275,7 @@ class MessagesFlowNode @AssistedInject constructor( formattedFileSize = event.content.formattedFileSize, fileExtension = event.content.fileExtension ), - mediaSource = mediaSource, + mediaSource = event.content.mediaSource, thumbnailSource = null, ) backstack.push(navTarget) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index d0ff255b02..799f6aa168 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -48,6 +48,7 @@ import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MessageEventType import io.element.android.libraries.matrix.api.room.roomMembers +import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin import io.element.android.libraries.matrix.api.verification.SessionVerificationService import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus @@ -233,7 +234,7 @@ class TimelinePresenter @AssistedInject constructor( if (eventId != null && firstVisibleIndex <= lastReadReceiptIndex.value && eventId != lastReadReceiptId.value) { lastReadReceiptIndex.value = firstVisibleIndex lastReadReceiptId.value = eventId - timeline.sendReadReceipt(eventId) + timeline.sendReadReceipt(eventId = eventId, receiptType = ReceiptType.READ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt index 47c8878825..0229282319 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsFlowNode.kt @@ -35,7 +35,7 @@ import io.element.android.features.roomdetails.impl.edit.RoomDetailsEditNode import io.element.android.features.roomdetails.impl.invite.RoomInviteMembersNode import io.element.android.features.roomdetails.impl.members.RoomMemberListNode import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsNode -import io.element.android.features.roomdetails.impl.members.details.avatar.RoomMemberAvatarPreviewNode +import io.element.android.features.roomdetails.impl.members.details.avatar.AvatarPreviewNode import io.element.android.features.roomdetails.impl.notificationsettings.RoomNotificationSettingsNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler @@ -89,7 +89,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( data class RoomMemberDetails(val roomMemberId: UserId) : NavTarget @Parcelize - data class MemberAvatarPreview(val userName: String, val avatarUrl: String) : NavTarget + data class AvatarPreview(val name: String, val avatarUrl: String) : NavTarget @Parcelize data object PollHistory : NavTarget @@ -115,8 +115,8 @@ class RoomDetailsFlowNode @AssistedInject constructor( backstack.push(NavTarget.RoomNotificationSettings(showUserDefinedSettingStyle = false)) } - override fun openAvatarPreview(username: String, url: String) { - backstack.push(NavTarget.MemberAvatarPreview(username, url)) + override fun openAvatarPreview(name: String, url: String) { + backstack.push(NavTarget.AvatarPreview(name, url)) } override fun openPollHistory() { @@ -160,7 +160,7 @@ class RoomDetailsFlowNode @AssistedInject constructor( is NavTarget.RoomMemberDetails -> { val callback = object : RoomMemberDetailsNode.Callback { override fun openAvatarPreview(username: String, avatarUrl: String) { - backstack.push(NavTarget.MemberAvatarPreview(username, avatarUrl)) + backstack.push(NavTarget.AvatarPreview(username, avatarUrl)) } override fun onStartDM(roomId: RoomId) { @@ -170,22 +170,22 @@ class RoomDetailsFlowNode @AssistedInject constructor( val plugins = listOf(RoomMemberDetailsNode.RoomMemberDetailsInput(navTarget.roomMemberId), callback) createNode(buildContext, plugins) } - is NavTarget.MemberAvatarPreview -> { + is NavTarget.AvatarPreview -> { // We need to fake the MimeType here for the viewer to work. val mimeType = MimeTypes.Images val input = MediaViewerNode.Inputs( mediaInfo = MediaInfo( - name = navTarget.userName, + name = navTarget.name, mimeType = mimeType, formattedFileSize = "", fileExtension = "" ), mediaSource = MediaSource(url = navTarget.avatarUrl), - thumbnailSource = MediaSource(url = navTarget.avatarUrl), + thumbnailSource = null, canDownload = false, canShare = false, ) - createNode(buildContext, listOf(input)) + createNode(buildContext, listOf(input)) } is NavTarget.PollHistory -> { diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt index d243bcebdf..dad02d0d07 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsNode.kt @@ -52,7 +52,7 @@ class RoomDetailsNode @AssistedInject constructor( fun openInviteMembers() fun editRoomDetails() fun openRoomNotificationSettings() - fun openAvatarPreview(username: String, url: String) + fun openAvatarPreview(name: String, url: String) fun openPollHistory() } @@ -116,8 +116,8 @@ class RoomDetailsNode @AssistedInject constructor( callbacks.forEach { it.editRoomDetails() } } - private fun openAvatarPreview(username: String, url: String) { - callbacks.forEach { it.openAvatarPreview(username, url) } + private fun openAvatarPreview(name: String, url: String) { + callbacks.forEach { it.openAvatarPreview(name, url) } } @Composable diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt index 8bdba4be73..2879995d82 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsView.kt @@ -16,6 +16,7 @@ package io.element.android.features.roomdetails.impl +import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -77,6 +78,7 @@ import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.CommonDrawables import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomNotificationMode +import io.element.android.libraries.matrix.api.room.getBestName import io.element.android.libraries.ui.strings.CommonStrings @Composable @@ -89,7 +91,7 @@ fun RoomDetailsView( openRoomMemberList: () -> Unit, openRoomNotificationSettings: () -> Unit, invitePeople: () -> Unit, - openAvatarPreview: (username: String, url: String) -> Unit, + openAvatarPreview: (name: String, url: String) -> Unit, openPollHistory: () -> Unit, modifier: Modifier = Modifier, ) { @@ -121,7 +123,10 @@ fun RoomDetailsView( avatarUrl = state.roomAvatarUrl, roomId = state.roomId, roomName = state.roomName, - roomAlias = state.roomAlias + roomAlias = state.roomAlias, + openAvatarPreview = { avatarUrl -> + openAvatarPreview(state.roomName, avatarUrl) + }, ) MainActionsSection( state = state, @@ -135,10 +140,8 @@ fun RoomDetailsView( avatarUrl = state.roomAvatarUrl ?: member.avatarUrl, userId = member.userId.value, userName = state.roomName, - openAvatarPreview = { - if (member.avatarUrl != null) { - openAvatarPreview(member.displayName ?: member.userId.value, member.avatarUrl!!) - } + openAvatarPreview = { avatarUrl -> + openAvatarPreview(member.getBestName(), avatarUrl) }, ) RoomMemberMainActionsSection(onShareUser = ::onShareMember) @@ -270,6 +273,7 @@ private fun RoomHeaderSection( roomId: String, roomName: String, roomAlias: String?, + openAvatarPreview: (url: String) -> Unit, modifier: Modifier = Modifier ) { Column( @@ -280,7 +284,9 @@ private fun RoomHeaderSection( ) { Avatar( avatarData = AvatarData(roomId, roomName, avatarUrl, AvatarSize.RoomHeader), - modifier = Modifier.size(70.dp) + modifier = Modifier + .size(70.dp) + .clickable(enabled = avatarUrl != null) { openAvatarPreview(avatarUrl!!) } ) Spacer(modifier = Modifier.height(24.dp)) Text( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/RoomMemberAvatarPreviewNode.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/AvatarPreviewNode.kt similarity index 95% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/RoomMemberAvatarPreviewNode.kt rename to features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/AvatarPreviewNode.kt index 8d186ae96c..ecd2806b88 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/RoomMemberAvatarPreviewNode.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/avatar/AvatarPreviewNode.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerNode import io.element.android.libraries.mediaviewer.api.viewer.MediaViewerPresenter @ContributesNode(RoomScope::class) -class RoomMemberAvatarPreviewNode @AssistedInject constructor( +class AvatarPreviewNode @AssistedInject constructor( @Assisted buildContext: BuildContext, @Assisted plugins: List, presenterFactory: MediaViewerPresenter.Factory, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 436707683d..ed5d174a9a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,7 +16,7 @@ datastore = "1.0.0" constraintlayout = "2.1.4" constraintlayout_compose = "1.0.1" lifecycle = "2.7.0-rc02" -activity = "1.8.1" +activity = "1.8.2" media3 = "1.2.0" # Compose @@ -91,7 +91,7 @@ androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.9.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha12" +androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-beta01" androidx_compose_ui = { module = "androidx.compose.ui:ui" } androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" } @@ -150,7 +150,7 @@ jsoup = "org.jsoup:jsoup:1.17.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.1" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.76" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.77" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt new file mode 100644 index 0000000000..aa445a6c99 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/text/TextSyleToTypeface.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.text + +import android.graphics.Typeface +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalFontFamilyResolver +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontStyle +import androidx.compose.ui.text.font.FontSynthesis +import androidx.compose.ui.text.font.FontWeight + +@Composable +fun TextStyle.rememberTypeface(): State { + val resolver: FontFamily.Resolver = LocalFontFamilyResolver.current + @Suppress("UNCHECKED_CAST") + return remember(resolver, this) { + resolver.resolve( + fontFamily = fontFamily, + fontWeight = fontWeight ?: FontWeight.Normal, + fontStyle = fontStyle ?: FontStyle.Normal, + fontSynthesis = fontSynthesis ?: FontSynthesis.All, + ) + } as State +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 3c9bd030b0..54c00572c5 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -32,3 +32,7 @@ data class RoomMember( enum class RoomMembershipState { BAN, INVITE, JOIN, KNOCK, LEAVE } + +fun RoomMember.getBestName(): String { + return displayName?.takeIf { it.isNotEmpty() } ?: userId.value +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt index 6c1433027b..2c22b80246 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/MatrixTimeline.kt @@ -44,5 +44,5 @@ interface MatrixTimeline : AutoCloseable { suspend fun paginateBackwards(requestSize: Int): Result suspend fun paginateBackwards(requestSize: Int, untilNumberOfItems: Int): Result suspend fun fetchDetailsForEvent(eventId: EventId): Result - suspend fun sendReadReceipt(eventId: EventId): Result + suspend fun sendReadReceipt(eventId: EventId, receiptType: ReceiptType): Result } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt new file mode 100644 index 0000000000..7f02285b7e --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/ReceiptType.kt @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.timeline + +enum class ReceiptType { + READ, + READ_PRIVATE, + FULLY_READ; +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt new file mode 100644 index 0000000000..02722e43dc --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/ReceiptTypeMapper.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.timeline + +import io.element.android.libraries.matrix.api.timeline.ReceiptType +import org.matrix.rustcomponents.sdk.ReceiptType as RustReceiptType + +internal fun ReceiptType.toRustReceiptType(): RustReceiptType = when (this) { + ReceiptType.READ -> RustReceiptType.READ + ReceiptType.READ_PRIVATE -> RustReceiptType.READ_PRIVATE + ReceiptType.FULLY_READ -> RustReceiptType.FULLY_READ +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index ebe6cfccfb..a7c0bef7c3 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.libraries.matrix.api.timeline.TimelineException import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.impl.timeline.item.event.EventMessageMapper @@ -230,9 +231,15 @@ class RustMatrixTimeline( return isInit.get() && paginationState.value.canBackPaginate } - override suspend fun sendReadReceipt(eventId: EventId) = withContext(dispatcher) { + override suspend fun sendReadReceipt( + eventId: EventId, + receiptType: ReceiptType, + ) = withContext(dispatcher) { runCatching { - innerTimeline.sendReadReceipt(eventId = eventId.value) + innerTimeline.sendReadReceipt( + receiptType = receiptType.toRustReceiptType(), + eventId = eventId.value, + ) } } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt index 51b504cb9f..94a8de2ced 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/timeline/FakeMatrixTimeline.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test.timeline import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.api.timeline.ReceiptType import io.element.android.tests.testutils.simulateLongTask import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.delay @@ -77,7 +78,10 @@ class FakeMatrixTimeline( Result.success(Unit) } - override suspend fun sendReadReceipt(eventId: EventId): Result = simulateLongTask { + override suspend fun sendReadReceipt( + eventId: EventId, + receiptType: ReceiptType, + ): Result = simulateLongTask { sendReadReceiptCount++ sendReadReceiptLatch?.complete(Unit) Result.success(Unit) diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt index 6876073d3b..ff93bb754a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpan.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.textcomposer.mentions import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF +import android.graphics.Typeface import android.text.style.ReplacementSpan import kotlin.math.roundToInt @@ -26,6 +27,9 @@ class MentionSpan( val type: Type, val backgroundColor: Int, val textColor: Int, + val startPadding: Int, + val endPadding: Int, + val typeface: Typeface = Typeface.DEFAULT, ) : ReplacementSpan() { override fun getSize(paint: Paint, text: CharSequence?, start: Int, end: Int, fm: Paint.FontMetricsInt?): Int { @@ -34,7 +38,8 @@ class MentionSpan( if (mentionText != text.toString()) { actualEnd = end + 1 } - return paint.measureText(mentionText, start, actualEnd).roundToInt() + 40 + paint.typeface = typeface + return paint.measureText(mentionText, start, actualEnd).roundToInt() + startPadding + endPadding } override fun draw(canvas: Canvas, text: CharSequence?, start: Int, end: Int, x: Float, top: Int, y: Int, bottom: Int, paint: Paint) { @@ -46,11 +51,12 @@ class MentionSpan( val textWidth = paint.measureText(mentionText, start, actualEnd) // Extra vertical space to add below the baseline (y). This helps us center the span vertically val extraVerticalSpace = y + paint.ascent() + paint.descent() - top - val rect = RectF(x, top.toFloat(), x + textWidth + 40, y.toFloat() + extraVerticalSpace) + val rect = RectF(x, top.toFloat(), x + textWidth + startPadding + endPadding, y.toFloat() + extraVerticalSpace) paint.color = backgroundColor canvas.drawRoundRect(rect, rect.height() / 2, rect.height() / 2, paint) paint.color = textColor - canvas.drawText(mentionText, start, actualEnd, x + 20, y.toFloat(), paint) + paint.typeface = typeface + canvas.drawText(mentionText, start, actualEnd, x + startPadding, y.toFloat(), paint) } private fun getActualText(text: CharSequence?, start: Int): String { diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt index 9fb459237c..f7b28d65e8 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/mentions/MentionSpanProvider.kt @@ -17,16 +17,24 @@ package io.element.android.libraries.textcomposer.mentions import android.graphics.Color +import android.graphics.Typeface import android.view.ViewGroup import android.widget.TextView +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection +import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.core.text.buildSpannedString +import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.designsystem.text.rememberTypeface import io.element.android.libraries.designsystem.theme.currentUserMentionPillBackground import io.element.android.libraries.designsystem.theme.currentUserMentionPillText import io.element.android.libraries.designsystem.theme.mentionPillBackground @@ -34,7 +42,6 @@ import io.element.android.libraries.designsystem.theme.mentionPillText import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.permalink.PermalinkParser -import io.element.android.compound.theme.ElementTheme @Stable class MentionSpanProvider( @@ -44,6 +51,10 @@ class MentionSpanProvider( private var otherTextColor: Int = 0, private var otherBackgroundColor: Int = Color.WHITE, ) { + private val paddingValues = PaddingValues(start = 4.dp, end = 6.dp) + + private val paddingValuesPx = mutableStateOf(0 to 0) + private val typeface = mutableStateOf(Typeface.DEFAULT) @Suppress("ComposableNaming") @Composable @@ -52,10 +63,18 @@ class MentionSpanProvider( currentUserBackgroundColor = ElementTheme.colors.currentUserMentionPillBackground.toArgb() otherTextColor = ElementTheme.colors.mentionPillText.toArgb() otherBackgroundColor = ElementTheme.colors.mentionPillBackground.toArgb() + + typeface.value = ElementTheme.typography.fontBodyLgMedium.rememberTypeface().value + with(LocalDensity.current) { + val leftPadding = paddingValues.calculateLeftPadding(LocalLayoutDirection.current).roundToPx() + val rightPadding = paddingValues.calculateRightPadding(LocalLayoutDirection.current).roundToPx() + paddingValuesPx.value = leftPadding to rightPadding + } } fun getMentionSpanFor(text: String, url: String): MentionSpan { val permalinkData = PermalinkParser.parse(url) + val (startPaddingPx, endPaddingPx) = paddingValuesPx.value return when { permalinkData is PermalinkData.UserLink -> { val isCurrentUser = permalinkData.userId == currentSessionId.value @@ -63,6 +82,9 @@ class MentionSpanProvider( type = MentionSpan.Type.USER, backgroundColor = if (isCurrentUser) currentUserBackgroundColor else otherBackgroundColor, textColor = if (isCurrentUser) currentUserTextColor else otherTextColor, + startPadding = startPaddingPx, + endPadding = endPaddingPx, + typeface = typeface.value, ) } text == "@room" && permalinkData is PermalinkData.FallbackLink -> { @@ -70,6 +92,9 @@ class MentionSpanProvider( type = MentionSpan.Type.USER, backgroundColor = otherBackgroundColor, textColor = otherTextColor, + startPadding = startPaddingPx, + endPadding = endPaddingPx, + typeface = typeface.value, ) } else -> { @@ -77,6 +102,9 @@ class MentionSpanProvider( type = MentionSpan.Type.ROOM, backgroundColor = otherBackgroundColor, textColor = otherTextColor, + startPadding = startPaddingPx, + endPadding = endPaddingPx, + typeface = typeface.value, ) } } diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_19_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_19_null,NEXUS_5,1.0,en].png index e681270fd2..c152a6cc4b 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_19_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Day-18_19_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8819c4ceeb21f96ba6d584ee78e4aca1a9cf2c87412f70e8f00eff09de61abc8 -size 43788 +oid sha256:e541cfb32e9d3095d6499c4c25e4bb4158c10886d59accf6006290bda344e8b2 +size 43927 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_20_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_20_null,NEXUS_5,1.0,en].png index 5005f06919..17f88d03b3 100644 --- a/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_20_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[l.textcomposer.mentions_MentionSpan_null_MentionSpan-Night-18_20_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ffbb340bd78c7806dd52be9454057b5ed4a3f9831f9ecb1c99b10c0759ba7f85 -size 37091 +oid sha256:06cd8d611bf8149d3c86b8d9a4aa5613dc9f32f89156081472f98d6410e98c4d +size 37107 diff --git a/tools/sdk/build_rust_sdk.sh b/tools/sdk/build_rust_sdk.sh index 3d7dc17059..93c0150f87 100755 --- a/tools/sdk/build_rust_sdk.sh +++ b/tools/sdk/build_rust_sdk.sh @@ -8,6 +8,7 @@ read -p "Do you want to build the Rust SDK from local source (yes/no) default to buildLocal=${buildLocal:-yes} date=$(gdate +%Y%m%d%H%M%S) +elementPwd=`pwd` # Ask for the Rust SDK local source path # if folder rustSdk/ exists, use it as default @@ -28,7 +29,7 @@ else cd matrix-rust-sdk-$date git checkout ${rustSdkBranch} rustSdkPath=$(pwd) - cd ../element-x-android + cd ${elementPwd} fi @@ -46,6 +47,8 @@ fi read -p "Do you want to build the app after (yes/no) default to yes? " buildApp buildApp=${buildApp:-yes} +cd ${elementPwd} + # If folder ../matrix-rust-components-kotlin does not exist, clone the repo if [ ! -d "../matrix-rust-components-kotlin" ]; then printf "\nFolder ../matrix-rust-components-kotlin does not exist. Cloning the repository into ../matrix-rust-components-kotlin.\n\n" @@ -59,9 +62,9 @@ git checkout main git pull printf "\nBuilding the SDK for aarch64-linux-android...\n\n" -./scripts/build.sh -p ${rustSdkPath} -m sdk -t aarch64-linux-android -o ../element-x-android/libraries/rustsdk +./scripts/build.sh -p ${rustSdkPath} -m sdk -t aarch64-linux-android -o ${elementPwd}/libraries/rustsdk -cd ../element-x-android +cd ${elementPwd} mv ./libraries/rustsdk/sdk-android-debug.aar ./libraries/rustsdk/matrix-rust-sdk.aar mkdir -p ./libraries/rustsdk/sdks cp ./libraries/rustsdk/matrix-rust-sdk.aar ./libraries/rustsdk/sdks/matrix-rust-sdk-${date}.aar