Merge branch 'develop' into feature/fga/space_settings_iteration

This commit is contained in:
ganfra
2025-12-15 16:06:06 +01:00
600 changed files with 3591 additions and 2388 deletions

View File

@@ -36,7 +36,7 @@ jobs:
./tools/localazy/importSupportedLocalesFromLocalazy.py ./tools/localazy/importSupportedLocalesFromLocalazy.py
./tools/test/generateAllScreenshots.py ./tools/test/generateAllScreenshots.py
- name: Create Pull Request for Strings - name: Create Pull Request for Strings
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with: with:
token: ${{ secrets.DANGER_GITHUB_API_TOKEN }} token: ${{ secrets.DANGER_GITHUB_API_TOKEN }}
commit-message: Sync Strings from Localazy commit-message: Sync Strings from Localazy

View File

@@ -23,7 +23,7 @@ jobs:
- name: Run SAS String script - name: Run SAS String script
run: ./tools/sas/import_sas_strings.py run: ./tools/sas/import_sas_strings.py
- name: Create Pull Request for SAS Strings - name: Create Pull Request for SAS Strings
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with: with:
commit-message: Sync SAS Strings commit-message: Sync SAS Strings
title: Sync SAS Strings title: Sync SAS Strings

View File

@@ -83,7 +83,7 @@ jobs:
# https://github.com/codecov/codecov-action # https://github.com/codecov/codecov-action
- name: ☂️ Upload coverage reports to codecov - name: ☂️ Upload coverage reports to codecov
uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
with: with:
fail_ci_if_error: true fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }} token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -199,6 +199,10 @@ android {
resources.pickFirsts += setOf( resources.pickFirsts += setOf(
"META-INF/versions/9/OSGI-INF/MANIFEST.MF", "META-INF/versions/9/OSGI-INF/MANIFEST.MF",
) )
jniLibs {
useLegacyPackaging = project.findProperty("useLegacyPackaging")?.toString()?.toBoolean()
}
} }
} }

View File

@@ -69,4 +69,4 @@
-keep class org.matrix.rustcomponents.sdk.** { *;} -keep class org.matrix.rustcomponents.sdk.** { *;}
-keep class uniffi.** { *;} -keep class uniffi.** { *;}
-keep class io.element.android.x.di.** { *; } -keep class io.element.android.x.di.** { *; }
-keepnames class io.element.android.x.** -keepclasseswithmembernames,allowoptimization,allowshrinking class io.element.android.** { *; }

View File

@@ -13,4 +13,5 @@ object LearnMoreConfig {
const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification" const val DEVICE_VERIFICATION_URL: String = "https://element.io/help#encryption-device-verification"
const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5" const val SECURE_BACKUP_URL: String = "https://element.io/help#encryption5"
const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18" const val IDENTITY_CHANGE_URL: String = "https://element.io/help#encryption18"
const val HISTORY_VISIBLE_URL: String = "https://element.io/en/help#e2ee-history-sharing"
} }

View File

@@ -8,7 +8,9 @@
package io.element.android.appnav.room.joined package io.element.android.appnav.room.joined
import android.app.Activity
import android.os.Parcelable import android.os.Parcelable
import androidx.activity.compose.LocalActivity
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@@ -96,6 +98,9 @@ class JoinedRoomLoadedFlowNode(
private val callback: Callback = callback() private val callback: Callback = callback()
override val graph = roomGraphFactory.create(inputs.room) override val graph = roomGraphFactory.create(inputs.room)
// This is an ugly hack to check activity recreation
private var currentActivity: Activity? = null
init { init {
lifecycle.subscribe( lifecycle.subscribe(
onCreate = { onCreate = {
@@ -115,8 +120,12 @@ class JoinedRoomLoadedFlowNode(
}, },
onDestroy = { onDestroy = {
Timber.v("OnDestroy") Timber.v("OnDestroy")
activeRoomsHolder.removeRoom(inputs.room.sessionId, inputs.room.roomId) // If we're just going through an activity recreation there's no need to destroy the Room object
inputs.room.destroy() // Destroying it would actually cause an issue where its methods can no longer be called
if (currentActivity?.isChangingConfigurations != true) {
activeRoomsHolder.removeRoom(inputs.room.sessionId, inputs.room.roomId)
inputs.room.destroy()
}
appNavigationStateService.onLeavingRoom(id) appNavigationStateService.onLeavingRoom(id)
} }
) )
@@ -289,6 +298,8 @@ class JoinedRoomLoadedFlowNode(
@Composable @Composable
override fun View(modifier: Modifier) { override fun View(modifier: Modifier) {
currentActivity = LocalActivity.current
BackstackView() BackstackView()
} }
} }

View File

@@ -46,7 +46,7 @@ allprojects {
config.from(files("$rootDir/tools/detekt/detekt.yml")) config.from(files("$rootDir/tools/detekt/detekt.yml"))
} }
dependencies { dependencies {
detektPlugins("io.nlopez.compose.rules:detekt:0.4.28") detektPlugins("io.nlopez.compose.rules:detekt:0.5.1")
detektPlugins(project(":tests:detekt-rules")) detektPlugins(project(":tests:detekt-rules"))
} }

View File

@@ -81,7 +81,7 @@ private fun SpaceAnnouncementHeader(
showBetaLabel = true, showBetaLabel = true,
subTitle = stringResource(id = R.string.screen_space_announcement_subtitle), subTitle = stringResource(id = R.string.screen_space_announcement_subtitle),
iconStyle = BigIcon.Style.Default( iconStyle = BigIcon.Style.Default(
vectorIcon = CompoundIcons.WorkspaceSolid(), vectorIcon = CompoundIcons.SpaceSolid(),
usePrimaryTint = true, usePrimaryTint = true,
), ),
) )

View File

@@ -40,7 +40,7 @@ import io.element.android.libraries.matrix.ui.room.address.RoomAddressValidityEf
import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@@ -132,7 +132,7 @@ class ConfigureRoomPresenter(
cameraPhotoPicker.launch() cameraPhotoPicker.launch()
} else { } else {
pendingPermissionRequest = true pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
AvatarAction.Remove -> dataStore.setAvatarUri(uri = null) AvatarAction.Remove -> dataStore.setAvatarUri(uri = null)
} }

View File

@@ -19,7 +19,7 @@ import dev.zacsweers.metro.AssistedInject
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.permissions.api.PermissionStateProvider import io.element.android.libraries.permissions.api.PermissionStateProvider
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter
import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider import io.element.android.services.toolbox.api.sdk.BuildVersionSdkIntProvider
@@ -58,7 +58,7 @@ class NotificationsOptInPresenter(
if (notificationsPermissionsState.permissionGranted) { if (notificationsPermissionsState.permissionGranted) {
callback.onNotificationsOptInFinished() callback.onNotificationsOptInFinished()
} else { } else {
notificationsPermissionsState.eventSink(PermissionsEvents.RequestPermissions) notificationsPermissionsState.eventSink(PermissionsEvent.RequestPermissions)
} }
} }
NotificationsOptInEvents.NotNowClicked -> { NotificationsOptInEvents.NotNowClicked -> {

View File

@@ -28,7 +28,7 @@ enum class HomeNavigationBarItem(
isSelected: Boolean, isSelected: Boolean,
) = when (this) { ) = when (this) {
Chats -> if (isSelected) CompoundIcons.ChatSolid() else CompoundIcons.Chat() Chats -> if (isSelected) CompoundIcons.ChatSolid() else CompoundIcons.Chat()
Spaces -> if (isSelected) CompoundIcons.WorkspaceSolid() else CompoundIcons.Workspace() Spaces -> if (isSelected) CompoundIcons.SpaceSolid() else CompoundIcons.Space()
} }
companion object { companion object {

View File

@@ -121,7 +121,6 @@ internal fun RoomSummaryRow(
) { ) {
NameAndTimestampRow( NameAndTimestampRow(
name = room.name, name = room.name,
latestEvent = room.latestEvent,
timestamp = room.timestamp, timestamp = room.timestamp,
isHighlighted = room.isHighlighted isHighlighted = room.isHighlighted
) )
@@ -138,7 +137,6 @@ internal fun RoomSummaryRow(
) { ) {
NameAndTimestampRow( NameAndTimestampRow(
name = room.name, name = room.name,
latestEvent = room.latestEvent,
timestamp = null, timestamp = null,
isHighlighted = room.isHighlighted isHighlighted = room.isHighlighted
) )
@@ -214,7 +212,6 @@ private fun RoomSummaryScaffoldRow(
@Composable @Composable
private fun NameAndTimestampRow( private fun NameAndTimestampRow(
name: String?, name: String?,
latestEvent: LatestEvent,
timestamp: String?, timestamp: String?,
isHighlighted: Boolean, isHighlighted: Boolean,
modifier: Modifier = Modifier modifier: Modifier = Modifier
@@ -236,28 +233,6 @@ private fun NameAndTimestampRow(
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis overflow = TextOverflow.Ellipsis
) )
// Picto
when (latestEvent) {
is LatestEvent.Sending -> {
Spacer(modifier = Modifier.width(4.dp))
Icon(
modifier = Modifier.size(16.dp),
imageVector = CompoundIcons.Time(),
contentDescription = null,
tint = ElementTheme.colors.iconTertiary,
)
}
is LatestEvent.Error -> {
Spacer(modifier = Modifier.width(4.dp))
Icon(
modifier = Modifier.size(16.dp),
imageVector = CompoundIcons.ErrorSolid(),
contentDescription = null,
tint = ElementTheme.colors.iconCriticalPrimary,
)
}
else -> Unit
}
} }
// Timestamp // Timestamp
Text( Text(
@@ -302,7 +277,6 @@ private fun MessagePreviewAndIndicatorRow(
) { ) {
Row( Row(
modifier = modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
horizontalArrangement = spacedBy(28.dp)
) { ) {
if (room.isTombstoned) { if (room.isTombstoned) {
Text( Text(
@@ -316,6 +290,16 @@ private fun MessagePreviewAndIndicatorRow(
) )
} else { } else {
if (room.latestEvent is LatestEvent.Error) { if (room.latestEvent is LatestEvent.Error) {
Icon(
modifier = Modifier
.padding(top = 2.dp)
.size(16.dp),
imageVector = CompoundIcons.ErrorSolid(),
// The last message contains the error.
contentDescription = null,
tint = ElementTheme.colors.iconCriticalPrimary,
)
Spacer(modifier = Modifier.width(6.dp))
Text( Text(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
text = stringResource(CommonStrings.common_message_failed_to_send), text = stringResource(CommonStrings.common_message_failed_to_send),
@@ -326,6 +310,17 @@ private fun MessagePreviewAndIndicatorRow(
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
) )
} else { } else {
if (room.latestEvent is LatestEvent.Sending) {
Icon(
modifier = Modifier
.padding(top = 2.dp)
.size(16.dp),
imageVector = CompoundIcons.Time(),
contentDescription = stringResource(CommonStrings.common_sending),
tint = ElementTheme.colors.iconTertiary,
)
Spacer(modifier = Modifier.width(6.dp))
}
val messagePreview = room.latestEvent.content() val messagePreview = room.latestEvent.content()
val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.orEmpty().toString()) val annotatedMessagePreview = messagePreview as? AnnotatedString ?: AnnotatedString(text = messagePreview.orEmpty().toString())
Text( Text(
@@ -339,7 +334,7 @@ private fun MessagePreviewAndIndicatorRow(
) )
} }
} }
Spacer(modifier = Modifier.width(16.dp))
// Call and unread // Call and unread
Row( Row(
modifier = Modifier modifier = Modifier

View File

@@ -71,7 +71,7 @@ class DefaultPinCodeManager(
lockScreenStore.onWrongPin() lockScreenStore.onWrongPin()
} }
} }
} catch (failure: Throwable) { } catch (_: Throwable) {
false false
} }
} }

View File

@@ -18,7 +18,7 @@ import androidx.compose.runtime.setValue
import dev.zacsweers.metro.Inject import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
@Inject @Inject
@@ -46,7 +46,7 @@ class QrCodeIntroPresenter(
canContinue = true canContinue = true
} else { } else {
pendingPermissionRequest = true pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
} }
} }

View File

@@ -106,7 +106,7 @@ private fun Content(
QrCodeCameraView( QrCodeCameraView(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) }, onScanQrCode = { state.eventSink.invoke(QrCodeScanEvents.QrCodeScanned(it)) },
renderPreview = state.isScanning, isScanning = state.isScanning,
) )
} }
} }

View File

@@ -68,6 +68,7 @@ dependencies {
implementation(libs.jsoup) implementation(libs.jsoup)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout)
implementation(libs.androidx.constraintlayout.compose) implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.media3.exoplayer) implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.ui) implementation(libs.androidx.media3.ui)
implementation(libs.sigpwned.emoji4j) implementation(libs.sigpwned.emoji4j)

View File

@@ -30,6 +30,7 @@ import io.element.android.features.messages.api.timeline.HtmlConverterProvider
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
@@ -101,6 +102,7 @@ class MessagesPresenter(
@Assisted private val timelinePresenter: Presenter<TimelineState>, @Assisted private val timelinePresenter: Presenter<TimelineState>,
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>, private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
private val identityChangeStatePresenter: Presenter<IdentityChangeState>, private val identityChangeStatePresenter: Presenter<IdentityChangeState>,
private val historyVisibleStatePresenter: Presenter<HistoryVisibleState>,
private val linkPresenter: Presenter<LinkState>, private val linkPresenter: Presenter<LinkState>,
@Assisted private val actionListPresenter: Presenter<ActionListState>, @Assisted private val actionListPresenter: Presenter<ActionListState>,
private val customReactionPresenter: Presenter<CustomReactionState>, private val customReactionPresenter: Presenter<CustomReactionState>,
@@ -152,6 +154,7 @@ class MessagesPresenter(
val timelineState = timelinePresenter.present() val timelineState = timelinePresenter.present()
val timelineProtectionState = timelineProtectionPresenter.present() val timelineProtectionState = timelineProtectionPresenter.present()
val identityChangeState = identityChangeStatePresenter.present() val identityChangeState = identityChangeStatePresenter.present()
val historyVisibleState = historyVisibleStatePresenter.present()
val actionListState = actionListPresenter.present() val actionListState = actionListPresenter.present()
val linkState = linkPresenter.present() val linkState = linkPresenter.present()
val customReactionState = customReactionPresenter.present() val customReactionState = customReactionPresenter.present()
@@ -274,6 +277,7 @@ class MessagesPresenter(
timelineState = timelineState, timelineState = timelineState,
timelineProtectionState = timelineProtectionState, timelineProtectionState = timelineProtectionState,
identityChangeState = identityChangeState, identityChangeState = identityChangeState,
historyVisibleState = historyVisibleState,
linkState = linkState, linkState = linkState,
actionListState = actionListState, actionListState = actionListState,
customReactionState = customReactionState, customReactionState = customReactionState,

View File

@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerState
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
@@ -40,6 +41,7 @@ data class MessagesState(
val timelineState: TimelineState, val timelineState: TimelineState,
val timelineProtectionState: TimelineProtectionState, val timelineProtectionState: TimelineProtectionState,
val identityChangeState: IdentityChangeState, val identityChangeState: IdentityChangeState,
val historyVisibleState: HistoryVisibleState,
val linkState: LinkState, val linkState: LinkState,
val actionListState: ActionListState, val actionListState: ActionListState,
val customReactionState: CustomReactionState, val customReactionState: CustomReactionState,

View File

@@ -14,7 +14,10 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState import io.element.android.features.messages.api.timeline.voicemessages.composer.aVoiceMessagePreviewState
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.crypto.identity.aRoomMemberIdentityStateChange
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
import io.element.android.features.messages.impl.link.LinkState import io.element.android.features.messages.impl.link.LinkState
import io.element.android.features.messages.impl.link.aLinkState import io.element.android.features.messages.impl.link.aLinkState
@@ -38,6 +41,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr
import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomcall.api.aStandByCallState import io.element.android.features.roomcall.api.aStandByCallState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
@@ -48,6 +52,7 @@ import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.libraries.textcomposer.model.MessageComposerMode
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@@ -83,6 +88,19 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
) )
), ),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange()))
),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
historyVisibleState = aHistoryVisibleState(showAlert = true)
),
aMessagesState(
composerState = aMessageComposerState(textEditorState = aTextEditorStateMarkdown()),
identityChangeState = anIdentityChangeState(listOf(aRoomMemberIdentityStateChange())),
historyVisibleState = aHistoryVisibleState(showAlert = true)
)
) )
} }
@@ -103,6 +121,7 @@ fun aMessagesState(
), ),
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(), timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
identityChangeState: IdentityChangeState = anIdentityChangeState(), identityChangeState: IdentityChangeState = anIdentityChangeState(),
historyVisibleState: HistoryVisibleState = aHistoryVisibleState(),
linkState: LinkState = aLinkState(), linkState: LinkState = aLinkState(),
readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(), readReceiptBottomSheetState: ReadReceiptBottomSheetState = aReadReceiptBottomSheetState(),
actionListState: ActionListState = anActionListState(), actionListState: ActionListState = anActionListState(),
@@ -125,6 +144,7 @@ fun aMessagesState(
voiceMessageComposerState = voiceMessageComposerState, voiceMessageComposerState = voiceMessageComposerState,
timelineProtectionState = timelineProtectionState, timelineProtectionState = timelineProtectionState,
identityChangeState = identityChangeState, identityChangeState = identityChangeState,
historyVisibleState = historyVisibleState,
linkState = linkState, linkState = linkState,
timelineState = timelineState, timelineState = timelineState,
readReceiptBottomSheetState = readReceiptBottomSheetState, readReceiptBottomSheetState = readReceiptBottomSheetState,
@@ -145,11 +165,9 @@ fun aMessagesState(
) )
fun aRoomMemberModerationState( fun aRoomMemberModerationState(
canKick: Boolean = false, permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT,
canBan: Boolean = false,
) = object : RoomMemberModerationState { ) = object : RoomMemberModerationState {
override val canKick: Boolean = canKick override val permissions: RoomMemberModerationPermissions = permissions
override val canBan: Boolean = canBan
override val eventSink: (RoomMemberModerationEvents) -> Unit = {} override val eventSink: (RoomMemberModerationEvents) -> Unit = {}
} }

View File

@@ -53,6 +53,7 @@ import io.element.android.features.messages.api.timeline.voicemessages.composer.
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.ActionListView
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
import io.element.android.features.messages.impl.link.LinkEvents import io.element.android.features.messages.impl.link.LinkEvents
import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.link.LinkView
@@ -486,10 +487,17 @@ private fun MessagesViewComposerBottomSheetContents(
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s). // Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
if (state.composerState.suggestions.isEmpty() && if (state.composerState.suggestions.isEmpty() &&
state.composerState.textEditorState is TextEditorState.Markdown) { state.composerState.textEditorState is TextEditorState.Markdown) {
IdentityChangeStateView( if (state.identityChangeState.roomMemberIdentityStateChanges.isNotEmpty()) {
state = state.identityChangeState, IdentityChangeStateView(
onLinkClick = onLinkClick, state = state.identityChangeState,
) onLinkClick = onLinkClick,
)
} else {
HistoryVisibleStateView(
state = state.historyVisibleState,
onLinkClick = onLinkClick,
)
}
} }
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull { val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
it.identityState == IdentityState.VerificationViolation it.identityState == IdentityState.VerificationViolation

View File

@@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import androidx.datastore.preferences.core.booleanPreferencesKey
import androidx.datastore.preferences.core.edit
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.hash.hash
import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
interface HistoryVisibleAcknowledgementRepository {
fun hasAcknowledged(roomId: RoomId): Flow<Boolean>
suspend fun setAcknowledged(roomId: RoomId, value: Boolean)
}
@ContributesBinding(SessionScope::class)
class DefaultHistoryVisibleAcknowledgementRepository(
sessionId: SessionId,
preferenceDataStoreFactory: PreferenceDataStoreFactory,
) : HistoryVisibleAcknowledgementRepository {
val store =
sessionId.value.hash().take(16).let { hash ->
preferenceDataStoreFactory.create("elementx_historyvisible_$hash")
}
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
return store.data.map { prefs ->
val acknowledged = prefs[booleanPreferencesKey(roomId.value)] ?: false
acknowledged
}
}
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
store.edit { prefs ->
prefs[booleanPreferencesKey(roomId.value)] = value
}
}
}

View File

@@ -0,0 +1,12 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
sealed interface HistoryVisibleEvent {
data object Acknowledge : HistoryVisibleEvent
}

View File

@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
data class HistoryVisibleState(
val showAlert: Boolean,
val eventSink: (HistoryVisibleEvent) -> Unit,
)

View File

@@ -0,0 +1,62 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch
@Inject
class HistoryVisibleStatePresenter(
private val featureFlagService: FeatureFlagService,
private val repository: HistoryVisibleAcknowledgementRepository,
private val room: JoinedRoom,
) : Presenter<HistoryVisibleState> {
@Composable
override fun present(): HistoryVisibleState {
val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.EnableKeyShareOnInvite).collectAsState(initial = false)
val roomInfo by room.roomInfoFlow.collectAsState()
// Implicitly assume the alert is initially acknowledged to avoid flashes in UI.
val acknowledged by repository.hasAcknowledged(room.roomId).collectAsState(initial = true)
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(roomInfo.historyVisibility, acknowledged) {
if (roomInfo.historyVisibility == RoomHistoryVisibility.Joined && acknowledged) {
repository.setAcknowledged(room.roomId, false)
}
}
fun handleEvent(event: HistoryVisibleEvent) {
when (event) {
is HistoryVisibleEvent.Acknowledge -> coroutineScope.setAcknowledged(room.roomId, true)
}
}
return HistoryVisibleState(
showAlert = isFeatureEnabled && roomInfo.historyVisibility != RoomHistoryVisibility.Joined && roomInfo.isEncrypted == true && !acknowledged,
eventSink = ::handleEvent,
)
}
private fun CoroutineScope.setAcknowledged(roomId: RoomId, value: Boolean) = launch {
repository.setAcknowledged(roomId, value)
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
class HistoryVisibleStateProvider : PreviewParameterProvider<HistoryVisibleState> {
override val values: Sequence<HistoryVisibleState>
get() = sequenceOf(
aHistoryVisibleState(showAlert = true),
)
}
internal fun aHistoryVisibleState(
showAlert: Boolean = false,
eventSink: (HistoryVisibleEvent) -> Unit = {},
) = HistoryVisibleState(
showAlert,
eventSink = eventSink,
)

View File

@@ -0,0 +1,81 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.LinkAnnotation
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.appconfig.LearnMoreConfig
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertLevel
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.ui.strings.CommonStrings
@Composable
fun HistoryVisibleStateView(
state: HistoryVisibleState,
onLinkClick: (String, Boolean) -> Unit,
modifier: Modifier = Modifier,
) {
if (!state.showAlert) {
return
}
ComposerAlertMolecule(
modifier = modifier,
avatar = null,
showIcon = true,
level = ComposerAlertLevel.Info,
content = buildAnnotatedString {
val learnMoreStr = stringResource(CommonStrings.action_learn_more)
val fullText = stringResource(CommonStrings.crypto_history_visible, learnMoreStr)
append(fullText)
val learnMoreStartIndex = fullText.lastIndexOf(learnMoreStr)
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
fontWeight = FontWeight.Bold,
color = ElementTheme.colors.textPrimary
),
start = learnMoreStartIndex,
end = learnMoreStartIndex + learnMoreStr.length,
)
addLink(
url = LinkAnnotation.Url(
url = LearnMoreConfig.HISTORY_VISIBLE_URL,
linkInteractionListener = {
onLinkClick(LearnMoreConfig.HISTORY_VISIBLE_URL, true)
}
),
start = learnMoreStartIndex,
end = learnMoreStartIndex + learnMoreStr.length,
)
},
submitText = stringResource(CommonStrings.action_dismiss),
onSubmitClick = { state.eventSink(HistoryVisibleEvent.Acknowledge) },
)
}
@PreviewsDayNight
@Composable
internal fun HistoryVisibleStateViewPreview(
@PreviewParameter(HistoryVisibleStateProvider::class) state: HistoryVisibleState,
) = ElementPreview {
HistoryVisibleStateView(
state = state,
onLinkClick = { _, _ -> },
)
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.MessagesView
import io.element.android.features.messages.impl.aMessagesState
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.textcomposer.model.aTextEditorStateMarkdown
@PreviewsDayNight
@Composable
internal fun MessagesViewWithHistoryVisiblePreview() = ElementPreview {
MessagesView(
state = aMessagesState(
composerState = aMessageComposerState(
textEditorState = aTextEditorStateMarkdown(
initialText = "",
initialFocus = false,
)
),
historyVisibleState = aHistoryVisibleState(showAlert = true),
),
onBackClick = {},
onRoomDetailsClick = {},
onEventContentClick = { _, _ -> false },
onUserDataClick = {},
onLinkClick = { _, _ -> },
onSendLocationClick = {},
onCreatePollClick = {},
onJoinCallClick = {},
onViewAllPinnedMessagesClick = {},
knockRequestsBannerView = {}
)
}

View File

@@ -11,6 +11,8 @@ package io.element.android.features.messages.impl.di
import dev.zacsweers.metro.BindingContainer import dev.zacsweers.metro.BindingContainer
import dev.zacsweers.metro.Binds import dev.zacsweers.metro.Binds
import dev.zacsweers.metro.ContributesTo import dev.zacsweers.metro.ContributesTo
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStatePresenter
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState import io.element.android.features.messages.impl.crypto.identity.IdentityChangeState
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStatePresenter
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
@@ -61,4 +63,7 @@ interface MessagesBindsModule {
@Binds @Binds
fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter<IdentityChangeState> fun bindIdentityChangeStatePresenter(presenter: IdentityChangeStatePresenter): Presenter<IdentityChangeState>
@Binds
fun bindHistoryVisibleStatePresenter(presenter: HistoryVisibleStatePresenter): Presenter<HistoryVisibleState>
} }

View File

@@ -64,7 +64,7 @@ import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory import io.element.android.libraries.mediaviewer.api.local.LocalMediaFactory
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService
@@ -286,7 +286,7 @@ class MessageComposerPresenter(
cameraPhotoPicker.launch() cameraPhotoPicker.launch()
} else { } else {
pendingEvent = event pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
} }
MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch { MessageComposerEvent.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
@@ -295,7 +295,7 @@ class MessageComposerPresenter(
cameraVideoPicker.launch() cameraVideoPicker.launch()
} else { } else {
pendingEvent = event pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
} }
MessageComposerEvent.PickAttachmentSource.Location -> { MessageComposerEvent.PickAttachmentSource.Location -> {

View File

@@ -112,7 +112,7 @@ fun EventDebugInfoView(
private fun prettyJSON(maybeJSON: String): String { private fun prettyJSON(maybeJSON: String): String {
return try { return try {
JSONObject(maybeJSON).toString(2) JSONObject(maybeJSON).toString(2)
} catch (e: JSONException) { } catch (_: JSONException) {
// Prefer not pretty-printing over crashing if the data is not actually JSON // Prefer not pretty-printing over crashing if the data is not actually JSON
maybeJSON maybeJSON
} }

View File

@@ -33,7 +33,7 @@ import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.mediaupload.api.MediaSenderFactory import io.element.android.libraries.mediaupload.api.MediaSenderFactory
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent
import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent
@@ -111,7 +111,7 @@ class DefaultVoiceMessageComposerPresenter(
} }
else -> { else -> {
Timber.i("Voice message permission needed") Timber.i("Voice message permission needed")
permissionState.eventSink(PermissionsEvents.RequestPermissions) permissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
} }
} }
@@ -176,10 +176,10 @@ class DefaultVoiceMessageComposerPresenter(
localCoroutineScope.deleteRecording() localCoroutineScope.deleteRecording()
} }
VoiceMessageComposerEvent.DismissPermissionsRationale -> { VoiceMessageComposerEvent.DismissPermissionsRationale -> {
permissionState.eventSink(PermissionsEvents.CloseDialog) permissionState.eventSink(PermissionsEvent.CloseDialog)
} }
VoiceMessageComposerEvent.AcceptPermissionRationale -> { VoiceMessageComposerEvent.AcceptPermissionRationale -> {
permissionState.eventSink(PermissionsEvents.OpenSystemSettingAndCloseDialog) permissionState.eventSink(PermissionsEvent.OpenSystemSettingAndCloseDialog)
} }
is VoiceMessageComposerEvent.LifecycleEvent -> handleLifecycleEvent(event.event) is VoiceMessageComposerEvent.LifecycleEvent -> handleLifecycleEvent(event.event)
VoiceMessageComposerEvent.DismissSendFailureDialog -> { VoiceMessageComposerEvent.DismissSendFailureDialog -> {

View File

@@ -17,6 +17,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.aHistoryVisibleState
import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState import io.element.android.features.messages.impl.crypto.identity.anIdentityChangeState
import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aMessageEvent
import io.element.android.features.messages.impl.link.aLinkState import io.element.android.features.messages.impl.link.aLinkState
@@ -1297,6 +1298,7 @@ class MessagesPresenterTest {
timelinePresenter = { aTimelineState(eventSink = timelineEventSink) }, timelinePresenter = { aTimelineState(eventSink = timelineEventSink) },
timelineProtectionPresenter = { aTimelineProtectionState() }, timelineProtectionPresenter = { aTimelineProtectionState() },
identityChangeStatePresenter = { anIdentityChangeState() }, identityChangeStatePresenter = { anIdentityChangeState() },
historyVisibleStatePresenter = { aHistoryVisibleState() },
linkPresenter = { aLinkState() }, linkPresenter = { aLinkState() },
actionListPresenter = { anActionListState(eventSink = actionListEventSink) }, actionListPresenter = { anActionListState(eventSink = actionListEventSink) },
customReactionPresenter = { aCustomReactionState() }, customReactionPresenter = { aCustomReactionState() },

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import io.element.android.libraries.matrix.api.core.RoomId
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
class FakeHistoryVisibleAcknowledgementRepository(
private val acknowledgements: MutableMap<RoomId, MutableStateFlow<Boolean>> = mutableMapOf()
) : HistoryVisibleAcknowledgementRepository {
override fun hasAcknowledged(roomId: RoomId): Flow<Boolean> {
return acknowledgements.getOrPut(roomId) {
MutableStateFlow(false)
}
}
override suspend fun setAcknowledged(roomId: RoomId, value: Boolean) {
val flow = acknowledgements.getOrPut(roomId) {
MutableStateFlow(value)
}
flow.emit(value)
}
companion object {
/**
* Create the repository with a pre-existing entry.
*/
fun withRoom(roomId: RoomId, acknowledged: Boolean = false): FakeHistoryVisibleAcknowledgementRepository {
return FakeHistoryVisibleAcknowledgementRepository(
mutableMapOf(
roomId to MutableStateFlow(acknowledged)
)
)
}
}
}

View File

@@ -0,0 +1,127 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.messages.impl.crypto.historyvisible
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.libraries.matrix.test.room.aRoomInfo
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
class HistoryVisibleStatePresenterTest {
@get:Rule
val warmUpRule = WarmUpRule()
@Test
fun `present - not visible if feature disabled`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, enabled = false, acknowledged = false)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room shared, unencrypted`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = false))
val presenter = createHistoryVisibleStatePresenter(room)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room joined, encrypted`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false))
val presenter = createHistoryVisibleStatePresenter(room)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - initial with room shared, encrypted, unacknowledged`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = false)
presenter.test {
val initialState = awaitItem()
assertThat(initialState.showAlert).isFalse()
val nextState = awaitItem()
assertThat(nextState.showAlert).isTrue()
}
}
@Test
fun `present - initial with room shared, encrypted, acknowledged`() = runTest {
val room = FakeJoinedRoom()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
val presenter = createHistoryVisibleStatePresenter(room, acknowledged = true)
presenter.test {
assertThat(awaitLastSequentialItem().showAlert).isFalse()
}
}
@Test
fun `present - transition from joined + unencrypted, to shared + encrypted`() = runTest {
val room = FakeJoinedRoom()
val featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.EnableKeyShareOnInvite.key to true))
val repository = FakeHistoryVisibleAcknowledgementRepository()
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = false))
val presenter = HistoryVisibleStatePresenter(
featureFlagService,
repository,
room,
)
presenter.test {
// emitted by the feature flag service(?)
assertThat(awaitItem().showAlert).isFalse()
// emitted state from room info assignment
assertThat(awaitItem().showAlert).isFalse()
// room is marked as encrypted
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Joined, isEncrypted = true))
assertThat(awaitItem().showAlert).isFalse()
// room history visibility is changed to shared
room.givenRoomInfo(aRoomInfo(historyVisibility = RoomHistoryVisibility.Shared, isEncrypted = true))
assertThat(awaitItem().showAlert).isTrue()
// alert is acknowledged
repository.setAcknowledged(room.roomId, true)
assertThat(awaitItem().showAlert).isFalse()
}
}
private fun createHistoryVisibleStatePresenter(
room: JoinedRoom = FakeJoinedRoom(),
enabled: Boolean = true,
acknowledged: Boolean = false
): HistoryVisibleStatePresenter {
return HistoryVisibleStatePresenter(
room = room,
featureFlagService = FakeFeatureFlagService(mapOf("feature.enableKeyShareOnInvite" to enabled)),
repository = FakeHistoryVisibleAcknowledgementRepository.withRoom(room.roomId, acknowledged)
)
}
}

View File

@@ -21,4 +21,5 @@ sealed interface DeveloperSettingsEvents {
data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents data class SetShowColorPicker(val show: Boolean) : DeveloperSettingsEvents
data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents data class ChangeBrandColor(val color: Color?) : DeveloperSettingsEvents
data object ClearCache : DeveloperSettingsEvents data object ClearCache : DeveloperSettingsEvents
data object VacuumStores : DeveloperSettingsEvents
} }

View File

@@ -29,6 +29,7 @@ import io.element.android.features.preferences.impl.developer.tracing.toLogLevel
import io.element.android.features.preferences.impl.model.EnabledFeature import io.element.android.features.preferences.impl.model.EnabledFeature
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@@ -61,6 +62,7 @@ class DeveloperSettingsPresenter(
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val enterpriseService: EnterpriseService, private val enterpriseService: EnterpriseService,
private val vacuumStoresUseCase: VacuumStoresUseCase,
) : Presenter<DeveloperSettingsState> { ) : Presenter<DeveloperSettingsState> {
@Composable @Composable
override fun present(): DeveloperSettingsState { override fun present(): DeveloperSettingsState {
@@ -151,6 +153,9 @@ class DeveloperSettingsPresenter(
is DeveloperSettingsEvents.SetShowColorPicker -> { is DeveloperSettingsEvents.SetShowColorPicker -> {
showColorPicker = event.show showColorPicker = event.show
} }
DeveloperSettingsEvents.VacuumStores -> coroutineScope.launch {
vacuumStoresUseCase()
}
} }
} }

View File

@@ -146,6 +146,14 @@ fun DeveloperSettingsView(
} }
val cache = state.cacheSize val cache = state.cacheSize
PreferenceCategory(title = "Cache") { PreferenceCategory(title = "Cache") {
ListItem(
headlineContent = {
Text("Vacuum stores")
},
onClick = {
state.eventSink(DeveloperSettingsEvents.VacuumStores)
}
)
ListItem( ListItem(
headlineContent = { headlineContent = {
Text("Clear cache") Text("Clear cache")

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.preferences.impl.tasks
import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.matrix.api.MatrixClient
import timber.log.Timber
fun interface VacuumStoresUseCase {
suspend operator fun invoke()
}
@ContributesBinding(AppScope::class)
class DefaultVacuumStoresUseCase(
private val matrixClient: MatrixClient,
) : VacuumStoresUseCase {
override suspend fun invoke() {
matrixClient.performDatabaseVacuum()
.onFailure { Timber.e(it, "Failed to vacuum stores") }
}
}

View File

@@ -35,7 +35,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -127,7 +127,7 @@ class EditUserProfilePresenter(
cameraPhotoPicker.launch() cameraPhotoPicker.launch()
} else { } else {
pendingPermissionRequest = true pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
AvatarAction.Remove -> { AvatarAction.Remove -> {
temporaryUriDeleter.delete(userAvatarUri?.toUri()) temporaryUriDeleter.delete(userAvatarUri?.toUri())

View File

@@ -17,6 +17,7 @@ import io.element.android.features.enterprise.test.FakeEnterpriseService
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
import io.element.android.features.preferences.impl.tasks.VacuumStoresUseCase
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@@ -212,6 +213,23 @@ class DeveloperSettingsPresenterTest {
} }
} }
@Test
fun `present - VacuumStores action invokes the VacuumStoresUseCase`() = runTest {
var vacuumCalled = false
val presenter = createDeveloperSettingsPresenter(
vacuumStoresUseCase = VacuumStoresUseCase {
vacuumCalled = true
}
)
presenter.test {
val state = awaitItem()
assertThat(vacuumCalled).isFalse()
state.eventSink(DeveloperSettingsEvents.VacuumStores)
skipItems(1)
assertThat(vacuumCalled).isTrue()
}
}
private fun createDeveloperSettingsPresenter( private fun createDeveloperSettingsPresenter(
sessionId: SessionId = A_SESSION_ID, sessionId: SessionId = A_SESSION_ID,
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService( featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(
@@ -230,6 +248,7 @@ class DeveloperSettingsPresenterTest {
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(), preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
buildMeta: BuildMeta = aBuildMeta(), buildMeta: BuildMeta = aBuildMeta(),
enterpriseService: EnterpriseService = FakeEnterpriseService(), enterpriseService: EnterpriseService = FakeEnterpriseService(),
vacuumStoresUseCase: VacuumStoresUseCase = VacuumStoresUseCase {},
): DeveloperSettingsPresenter { ): DeveloperSettingsPresenter {
return DeveloperSettingsPresenter( return DeveloperSettingsPresenter(
sessionId = sessionId, sessionId = sessionId,
@@ -240,6 +259,7 @@ class DeveloperSettingsPresenterTest {
appPreferencesStore = preferencesStore, appPreferencesStore = preferencesStore,
buildMeta = buildMeta, buildMeta = buildMeta,
enterpriseService = enterpriseService, enterpriseService = enterpriseService,
vacuumStoresUseCase = vacuumStoresUseCase,
) )
} }
} }

View File

@@ -2,12 +2,9 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_room_change_permissions_administrators">"Správca"</string> <string name="screen_room_change_permissions_administrators">"Správca"</string>
<string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string> <string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string>
<string name="screen_room_change_permissions_change_settings">"Zmeniť nastavenia"</string>
<string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string> <string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string>
<string name="screen_room_change_permissions_everyone">"Člen"</string> <string name="screen_room_change_permissions_everyone">"Člen"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvať ľudí"</string> <string name="screen_room_change_permissions_invite_people">"Pozvať ľudí"</string>
<string name="screen_room_change_permissions_manage_space">"Spravovať priestor"</string>
<string name="screen_room_change_permissions_manage_space_rooms">"Spravovať miestnosti"</string>
<string name="screen_room_change_permissions_member_moderation">"Spravovať členov"</string> <string name="screen_room_change_permissions_member_moderation">"Spravovať členov"</string>
<string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string> <string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_change_permissions_moderators">"Moderátor"</string> <string name="screen_room_change_permissions_moderators">"Moderátor"</string>
@@ -17,7 +14,6 @@
<string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string> <string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string>
<string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string> <string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string>
<string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string> <string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string>
<string name="screen_room_change_permissions_title">"Povolenia"</string>
<string name="screen_room_change_role_administrators_title">"Upraviť správcov"</string> <string name="screen_room_change_role_administrators_title">"Upraviť správcov"</string>
<string name="screen_room_change_role_confirm_add_admin_description">"Túto akciu nebudete môcť vrátiť späť. Zvyšujete úroveň používateľa na rovnakú úroveň výkonu ako máte vy."</string> <string name="screen_room_change_role_confirm_add_admin_description">"Túto akciu nebudete môcť vrátiť späť. Zvyšujete úroveň používateľa na rovnakú úroveň výkonu ako máte vy."</string>
<string name="screen_room_change_role_confirm_add_admin_title">"Pridať správcu?"</string> <string name="screen_room_change_role_confirm_add_admin_title">"Pridať správcu?"</string>

View File

@@ -26,7 +26,7 @@ data class RoomMemberListState(
val moderationState: RoomMemberModerationState, val moderationState: RoomMemberModerationState,
val eventSink: (RoomMemberListEvents) -> Unit, val eventSink: (RoomMemberListEvents) -> Unit,
) { ) {
val showBannedSection: Boolean = moderationState.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
} }
enum class SelectedSection { enum class SelectedSection {

View File

@@ -10,6 +10,7 @@ package io.element.android.features.roomdetails.impl.members
import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.map import io.element.android.libraries.architecture.map
@@ -99,8 +100,10 @@ fun aRoomMemberModerationState(
canKick: Boolean = false, canKick: Boolean = false,
): RoomMemberModerationState { ): RoomMemberModerationState {
return object : RoomMemberModerationState { return object : RoomMemberModerationState {
override val canKick: Boolean = canKick override val permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions(
override val canBan: Boolean = canBan canBan = canBan,
canKick = canKick,
)
override val eventSink: (RoomMemberModerationEvents) -> Unit = {} override val eventSink: (RoomMemberModerationEvents) -> Unit = {}
} }
} }

View File

@@ -84,6 +84,10 @@
<string name="screen_room_member_list_manage_member_unban_title">"Eemalda suhtluskeeld jututoas"</string> <string name="screen_room_member_list_manage_member_unban_title">"Eemalda suhtluskeeld jututoas"</string>
<string name="screen_room_member_list_mode_banned">"Suhtluskeeluga kasutajad"</string> <string name="screen_room_member_list_mode_banned">"Suhtluskeeluga kasutajad"</string>
<string name="screen_room_member_list_mode_members">"Liikmed"</string> <string name="screen_room_member_list_mode_members">"Liikmed"</string>
<plurals name="screen_room_member_list_pending_header_title">
<item quantity="one">"%1$d saatis kutse"</item>
<item quantity="other">"%1$d saatis kutse"</item>
</plurals>
<string name="screen_room_member_list_pending_status">"Ootel"</string> <string name="screen_room_member_list_pending_status">"Ootel"</string>
<string name="screen_room_member_list_role_administrator">"Peakasutajad"</string> <string name="screen_room_member_list_role_administrator">"Peakasutajad"</string>
<string name="screen_room_member_list_role_moderator">"Moderaatorid"</string> <string name="screen_room_member_list_role_moderator">"Moderaatorid"</string>
@@ -133,6 +137,7 @@ Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega
<string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Halda kogukondi"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string> <string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string>

View File

@@ -141,9 +141,13 @@ Nous ne recommandons pas dactiver le chiffrement pour les salons que tout le
<string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string>
<string name="screen_security_and_privacy_room_access_footer">"Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gérer les espaces"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Accès"</string> <string name="screen_security_and_privacy_room_access_section_header">"Accès"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Toute personne se trouvant dans un espace autorisé peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Toute personne de lespace %1$s peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string> <string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string> <string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string> <string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string>

View File

@@ -1,19 +1,21 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string> <string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_edit_room_address_title">"Adresa miestnosti"</string> <string name="screen_edit_room_address_title">"Upraviť adresu"</string>
<string name="screen_notification_settings_edit_failed_updating_default_mode">"Pri aktualizácii nastavenia oznámenia došlo k chybe."</string> <string name="screen_notification_settings_edit_failed_updating_default_mode">"Pri aktualizácii nastavenia oznámenia došlo k chybe."</string>
<string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie."</string> <string name="screen_notification_settings_mentions_only_disclaimer">"Váš domovský server nepodporuje túto možnosť v šifrovaných miestnostiach, v niektorých miestnostiach nemusíte dostať upozornenie."</string>
<string name="screen_polls_history_title">"Ankety"</string> <string name="screen_polls_history_title">"Ankety"</string>
<string name="screen_room_change_permissions_administrators">"Iba správcovia"</string> <string name="screen_room_change_permissions_administrators">"Správca"</string>
<string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string> <string name="screen_room_change_permissions_ban_people">"Zakázať ľudí"</string>
<string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string> <string name="screen_room_change_permissions_delete_messages">"Odstrániť správy"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvite ľudí a prijmite žiadosti o pripojenie"</string> <string name="screen_room_change_permissions_everyone">"Člen"</string>
<string name="screen_room_change_permissions_invite_people">"Pozvať ľudí"</string>
<string name="screen_room_change_permissions_member_moderation">"Spravovať členov"</string>
<string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string> <string name="screen_room_change_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_change_permissions_moderators">"Správcovia a moderátori"</string> <string name="screen_room_change_permissions_moderators">"Moderátor"</string>
<string name="screen_room_change_permissions_remove_people">"Odstrániť ľudí a odmietnuť žiadosti o pripojenie"</string> <string name="screen_room_change_permissions_remove_people">"Odstrániť ľudí"</string>
<string name="screen_room_change_permissions_room_avatar">"Zmeniť obrázok miestnosti"</string> <string name="screen_room_change_permissions_room_avatar">"Zmeniť obrázok miestnosti"</string>
<string name="screen_room_change_permissions_room_details">"Upraviť miestnosť"</string> <string name="screen_room_change_permissions_room_details">"Upraviť podrobnosti"</string>
<string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string> <string name="screen_room_change_permissions_room_name">"Zmeniť názov miestnosti"</string>
<string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string> <string name="screen_room_change_permissions_room_topic">"Zmeniť tému miestnosti"</string>
<string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string> <string name="screen_room_change_permissions_send_messages">"Odoslať správy"</string>
@@ -40,7 +42,7 @@
<string name="screen_room_details_badge_encrypted">"Zašifrované"</string> <string name="screen_room_details_badge_encrypted">"Zašifrované"</string>
<string name="screen_room_details_badge_not_encrypted">"Nešifrované"</string> <string name="screen_room_details_badge_not_encrypted">"Nešifrované"</string>
<string name="screen_room_details_badge_public">"Verejná miestnosť"</string> <string name="screen_room_details_badge_public">"Verejná miestnosť"</string>
<string name="screen_room_details_edit_room_title">"Upraviť miestnosť"</string> <string name="screen_room_details_edit_room_title">"Upraviť podrobnosti"</string>
<string name="screen_room_details_edition_error">"Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť."</string> <string name="screen_room_details_edition_error">"Vyskytla sa neznáma chyba a informácie nebolo možné zmeniť."</string>
<string name="screen_room_details_edition_error_title">"Nepodarilo sa aktualizovať miestnosť"</string> <string name="screen_room_details_edition_error_title">"Nepodarilo sa aktualizovať miestnosť"</string>
<string name="screen_room_details_encryption_enabled_subtitle">"Správy sú zabezpečené zámkami. Jedine vy a príjemcovia máte jedinečné kľúče na ich odomknutie."</string> <string name="screen_room_details_encryption_enabled_subtitle">"Správy sú zabezpečené zámkami. Jedine vy a príjemcovia máte jedinečné kľúče na ich odomknutie."</string>
@@ -69,6 +71,13 @@
<string name="screen_room_details_topic_title">"Téma"</string> <string name="screen_room_details_topic_title">"Téma"</string>
<string name="screen_room_details_updating_room">"Aktualizácia miestnosti…"</string> <string name="screen_room_details_updating_room">"Aktualizácia miestnosti…"</string>
<string name="screen_room_member_list_banned_empty">"Neexistujú žiadni zablokovaní používatelia."</string> <string name="screen_room_member_list_banned_empty">"Neexistujú žiadni zablokovaní používatelia."</string>
<plurals name="screen_room_member_list_banned_header_title">
<item quantity="one">"%1$d zakázaný"</item>
<item quantity="few">"%1$d zakázaní"</item>
<item quantity="other">"%1$d zakázaných"</item>
</plurals>
<string name="screen_room_member_list_empty_search_subtitle">"Skontrolujte preklepy alebo skúste nové vyhľadávanie"</string>
<string name="screen_room_member_list_empty_search_title">"Žiadne výsledky pre „%1$s“"</string>
<plurals name="screen_room_member_list_header_title"> <plurals name="screen_room_member_list_header_title">
<item quantity="one">"%1$d osoba"</item> <item quantity="one">"%1$d osoba"</item>
<item quantity="few">"%1$d osoby"</item> <item quantity="few">"%1$d osoby"</item>
@@ -81,8 +90,14 @@
<string name="screen_room_member_list_manage_member_unban_title">"Zrušiť zákaz prístupu do miestnosti"</string> <string name="screen_room_member_list_manage_member_unban_title">"Zrušiť zákaz prístupu do miestnosti"</string>
<string name="screen_room_member_list_mode_banned">"Zakázaní"</string> <string name="screen_room_member_list_mode_banned">"Zakázaní"</string>
<string name="screen_room_member_list_mode_members">"Členovia"</string> <string name="screen_room_member_list_mode_members">"Členovia"</string>
<string name="screen_room_member_list_role_administrator">"Iba správcovia"</string> <plurals name="screen_room_member_list_pending_header_title">
<string name="screen_room_member_list_role_moderator">"Správcovia a moderátori"</string> <item quantity="one">"%1$d pozvaný"</item>
<item quantity="few">"%1$d pozvaní"</item>
<item quantity="other">"%1$d pozvaných"</item>
</plurals>
<string name="screen_room_member_list_pending_status">"Čaká na schválenie"</string>
<string name="screen_room_member_list_role_administrator">"Správca"</string>
<string name="screen_room_member_list_role_moderator">"Moderátor"</string>
<string name="screen_room_member_list_role_owner">"Vlastník"</string> <string name="screen_room_member_list_role_owner">"Vlastník"</string>
<string name="screen_room_member_list_room_members_header_title">"Členovia miestnosti"</string> <string name="screen_room_member_list_room_members_header_title">"Členovia miestnosti"</string>
<string name="screen_room_member_list_unbanning_user">"Zrušenie zákazu %1$s"</string> <string name="screen_room_member_list_unbanning_user">"Zrušenie zákazu %1$s"</string>
@@ -109,14 +124,15 @@
<string name="screen_room_roles_and_permissions_messages_and_content">"Správy a obsah"</string> <string name="screen_room_roles_and_permissions_messages_and_content">"Správy a obsah"</string>
<string name="screen_room_roles_and_permissions_moderators">"Moderátori"</string> <string name="screen_room_roles_and_permissions_moderators">"Moderátori"</string>
<string name="screen_room_roles_and_permissions_owners">"Vlastníci"</string> <string name="screen_room_roles_and_permissions_owners">"Vlastníci"</string>
<string name="screen_room_roles_and_permissions_permissions_header">"Povolenia"</string>
<string name="screen_room_roles_and_permissions_reset">"Obnoviť povolenia"</string> <string name="screen_room_roles_and_permissions_reset">"Obnoviť povolenia"</string>
<string name="screen_room_roles_and_permissions_reset_confirm_description">"Po obnovení oprávnení prídete o aktuálne nastavenia."</string> <string name="screen_room_roles_and_permissions_reset_confirm_description">"Po obnovení oprávnení prídete o aktuálne nastavenia."</string>
<string name="screen_room_roles_and_permissions_reset_confirm_title">"Obnoviť oprávnenia?"</string> <string name="screen_room_roles_and_permissions_reset_confirm_title">"Obnoviť oprávnenia?"</string>
<string name="screen_room_roles_and_permissions_roles_header">"Roly"</string> <string name="screen_room_roles_and_permissions_roles_header">"Roly"</string>
<string name="screen_room_roles_and_permissions_room_details">"Podrobnosti o miestnosti"</string> <string name="screen_room_roles_and_permissions_room_details">"Podrobnosti o miestnosti"</string>
<string name="screen_room_roles_and_permissions_title">"Roly a povolenia"</string> <string name="screen_room_roles_and_permissions_title">"Roly a povolenia"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu miestnosti"</string> <string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Ktokoľvek môže požiadať o pripojenie do miestnosti, ale správca alebo moderátor bude musieť žiadosť prijať."</string> <string name="screen_security_and_privacy_ask_to_join_option_description">"Všetci musia požiadať o prístup."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string> <string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string> <string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. <string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti.
@@ -126,17 +142,22 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string> <string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string>
<string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string> <string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string>
<string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Ktokoľvek môže nájsť a pripojiť sa"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Pripojiť sa môže ktokoľvek."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Ľudia sa môžu pripojiť len vtedy, ak sú pozvaní"</string> <string name="screen_security_and_privacy_room_access_footer">"Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovať priestory"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup do miestnosti"</string> <string name="screen_security_and_privacy_room_access_section_header">"Prístup"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Ktokoľvek v povolených priestoroch sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Ktokoľvek v %1$s sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string> <string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string> <string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string> <string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa miestnosti"</string> <string name="screen_security_and_privacy_room_address_section_header">"Adresa"</string>
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string> <string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné v adresári verejných miestností"</string> <string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Umožniť nájdenie vyhľadávaním vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné vo verejnom adresári"</string>
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string> <string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string> <string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string>
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string> <string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string>
@@ -144,6 +165,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. <string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými.
Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string> Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string>
<string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string> <string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť miestnosti"</string> <string name="screen_security_and_privacy_room_visibility_section_footer">"Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými."</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť"</string>
<string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string> <string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string>
</resources> </resources>

View File

@@ -69,7 +69,7 @@
<string name="screen_room_details_share_room_title">"Share room"</string> <string name="screen_room_details_share_room_title">"Share room"</string>
<string name="screen_room_details_title">"Room info"</string> <string name="screen_room_details_title">"Room info"</string>
<string name="screen_room_details_topic_title">"Topic"</string> <string name="screen_room_details_topic_title">"Topic"</string>
<string name="screen_room_details_updating_room">"Updating room…"</string> <string name="screen_room_details_updating_room">"Updating details…"</string>
<string name="screen_room_member_list_banned_empty">"There are no banned users."</string> <string name="screen_room_member_list_banned_empty">"There are no banned users."</string>
<plurals name="screen_room_member_list_banned_header_title"> <plurals name="screen_room_member_list_banned_header_title">
<item quantity="one">"%1$d Banned"</item> <item quantity="one">"%1$d Banned"</item>
@@ -129,8 +129,10 @@
<string name="screen_room_roles_and_permissions_room_details">"Room details"</string> <string name="screen_room_roles_and_permissions_room_details">"Room details"</string>
<string name="screen_room_roles_and_permissions_title">"Roles &amp; permissions"</string> <string name="screen_room_roles_and_permissions_title">"Roles &amp; permissions"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Add address"</string> <string name="screen_security_and_privacy_add_room_address_action">"Add address"</string>
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Anyone in authorized spaces can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string> <string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string> <string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string>
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Anyone in %1$s can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string> <string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room. <string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.
No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly. No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.

View File

@@ -37,7 +37,7 @@ import io.element.android.libraries.matrix.ui.media.AvatarAction
import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider import io.element.android.libraries.mediaupload.api.MediaOptimizationConfigProvider
import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsEvents import io.element.android.libraries.permissions.api.PermissionsEvent
import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.api.PermissionsPresenter
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -151,7 +151,7 @@ class RoomDetailsEditPresenter(
cameraPhotoPicker.launch() cameraPhotoPicker.launch()
} else { } else {
pendingPermissionRequest = true pendingPermissionRequest = true
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions) cameraPermissionState.eventSink(PermissionsEvent.RequestPermissions)
} }
AvatarAction.Remove -> { AvatarAction.Remove -> {
temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri()) temporaryUriDeleter.delete(roomAvatarUriEdited?.toUri())

View File

@@ -3,5 +3,5 @@
<string name="screen_room_details_edit_room_title">"Edit details"</string> <string name="screen_room_details_edit_room_title">"Edit details"</string>
<string name="screen_room_details_edition_error">"There was an unknown error and the information couldn\'t be changed."</string> <string name="screen_room_details_edition_error">"There was an unknown error and the information couldn\'t be changed."</string>
<string name="screen_room_details_edition_error_title">"Unable to update room"</string> <string name="screen_room_details_edition_error_title">"Unable to update room"</string>
<string name="screen_room_details_updating_room">"Updating room…"</string> <string name="screen_room_details_updating_room">"Updating details…"</string>
</resources> </resources>

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.features.roommembermoderation.api
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPermissions
data class RoomMemberModerationPermissions(
val canKick: Boolean,
val canBan: Boolean,
) {
companion object {
val DEFAULT = RoomMemberModerationPermissions(
canKick = false,
canBan = false,
)
}
}
fun RoomPermissions.roomMemberModerationPermissions(): RoomMemberModerationPermissions {
return RoomMemberModerationPermissions(
canKick = canOwnUserKick(),
canBan = canOwnUserBan(),
)
}

View File

@@ -12,8 +12,7 @@ import androidx.compose.runtime.Immutable
@Immutable @Immutable
interface RoomMemberModerationState { interface RoomMemberModerationState {
val canKick: Boolean val permissions: RoomMemberModerationPermissions
val canBan: Boolean
val eventSink: (RoomMemberModerationEvents) -> Unit val eventSink: (RoomMemberModerationEvents) -> Unit
} }

View File

@@ -10,14 +10,14 @@ package io.element.android.features.roommembermoderation.impl
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
data class InternalRoomMemberModerationState( data class InternalRoomMemberModerationState(
override val canKick: Boolean, override val permissions: RoomMemberModerationPermissions,
override val canBan: Boolean,
val selectedUser: MatrixUser?, val selectedUser: MatrixUser?,
val actions: ImmutableList<ModerationActionState>, val actions: ImmutableList<ModerationActionState>,
val kickUserAsyncAction: AsyncAction<Unit>, val kickUserAsyncAction: AsyncAction<Unit>,

View File

@@ -12,6 +12,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
@@ -83,8 +84,7 @@ fun anAlice() = MatrixUser(
) )
fun aRoomMembersModerationState( fun aRoomMembersModerationState(
canKick: Boolean = false, permissions: RoomMemberModerationPermissions = RoomMemberModerationPermissions.DEFAULT,
canBan: Boolean = false,
selectedUser: MatrixUser? = null, selectedUser: MatrixUser? = null,
actions: List<ModerationActionState> = emptyList(), actions: List<ModerationActionState> = emptyList(),
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized, kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
@@ -92,8 +92,7 @@ fun aRoomMembersModerationState(
unbanUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized, unbanUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (RoomMemberModerationEvents) -> Unit = {}, eventSink: (RoomMemberModerationEvents) -> Unit = {},
) = InternalRoomMemberModerationState( ) = InternalRoomMemberModerationState(
canKick = canKick, permissions = permissions,
canBan = canBan,
selectedUser = selectedUser, selectedUser = selectedUser,
actions = actions.toImmutableList(), actions = actions.toImmutableList(),
kickUserAsyncAction = kickUserAsyncAction, kickUserAsyncAction = kickUserAsyncAction,

View File

@@ -21,7 +21,9 @@ import im.vector.app.features.analytics.plan.RoomModeration
import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.features.roommembermoderation.api.roomMemberModerationPermissions
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.architecture.runUpdatingState
@@ -55,11 +57,8 @@ class RoomMemberModerationPresenter(
override fun present(): RoomMemberModerationState { override fun present(): RoomMemberModerationState {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
val permissions by room.permissionsAsState(Permissions()) { perms -> val permissions by room.permissionsAsState(RoomMemberModerationPermissions.DEFAULT) { perms ->
Permissions( perms.roomMemberModerationPermissions()
canKick = perms.canOwnUserKick(),
canBan = perms.canOwnUserBan(),
)
} }
val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value) val currentUserMemberPowerLevel = room.userPowerLevelAsState(syncUpdateFlow.value)
@@ -136,8 +135,7 @@ class RoomMemberModerationPresenter(
} }
return InternalRoomMemberModerationState( return InternalRoomMemberModerationState(
canKick = permissions.canKick, permissions = permissions,
canBan = permissions.canBan,
selectedUser = selectedUser, selectedUser = selectedUser,
actions = moderationActions.value, actions = moderationActions.value,
kickUserAsyncAction = kickUserAsyncAction.value, kickUserAsyncAction = kickUserAsyncAction.value,
@@ -149,7 +147,7 @@ class RoomMemberModerationPresenter(
private fun computeModerationActions( private fun computeModerationActions(
member: RoomMember?, member: RoomMember?,
permissions: Permissions, permissions: RoomMemberModerationPermissions,
currentUserMemberPowerLevel: Long, currentUserMemberPowerLevel: Long,
): ImmutableList<ModerationActionState> { ): ImmutableList<ModerationActionState> {
return buildList { return buildList {

View File

@@ -13,6 +13,7 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.features.roommembermoderation.api.ModerationAction import io.element.android.features.roommembermoderation.api.ModerationAction
import io.element.android.features.roommembermoderation.api.ModerationActionState import io.element.android.features.roommembermoderation.api.ModerationActionState
import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents import io.element.android.features.roommembermoderation.api.RoomMemberModerationEvents
import io.element.android.features.roommembermoderation.api.RoomMemberModerationPermissions
import io.element.android.features.roommembermoderation.api.RoomMemberModerationState import io.element.android.features.roommembermoderation.api.RoomMemberModerationState
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
@@ -49,8 +50,7 @@ class RoomMemberModerationPresenterTest {
val room = aJoinedRoom() val room = aJoinedRoom()
createRoomMemberModerationPresenter(room = room).test { createRoomMemberModerationPresenter(room = room).test {
val initialState = awaitState() val initialState = awaitState()
assertThat(initialState.canKick).isFalse() assertThat(initialState.permissions).isEqualTo(RoomMemberModerationPermissions.DEFAULT)
assertThat(initialState.canBan).isFalse()
assertThat(initialState.selectedUser).isNull() assertThat(initialState.selectedUser).isNull()
assertThat(initialState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.banUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(initialState.kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.kickUserAsyncAction).isEqualTo(AsyncAction.Uninitialized)

View File

@@ -218,7 +218,7 @@ private fun RoomAccessSection(
Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description)) Text(text = stringResource(R.string.screen_security_and_privacy_room_access_space_members_option_unavailable_description))
}, },
trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false), trailingContent = ListItemContent.RadioButton(selected = edited == SecurityAndPrivacyRoomAccess.SpaceMember, enabled = false),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Workspace())), leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Space())),
enabled = false, enabled = false,
) )
} }

View File

@@ -15,6 +15,7 @@ Me ei soovita krüptimise kasutamist selliste avalike jututubade puhul, millega
<string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Võta läbiv krüptimine kasutusele"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Kõik võivad jututoaga liituda"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Kõik"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Halda kogukondi"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Liituda saab vaid kutse olemasolul"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Vaid kutsega"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string> <string name="screen_security_and_privacy_room_access_section_header">"Ligipääs"</string>

View File

@@ -15,9 +15,13 @@ Nous ne recommandons pas dactiver le chiffrement pour les salons que tout le
<string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Activer le chiffrement de bout en bout"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Tout le monde peut rejoindre."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Tout le monde"</string>
<string name="screen_security_and_privacy_room_access_footer">"Choisissez les espaces dont les membres peuvent rejoindre ce salon sans invitation. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Gérer les espaces"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string> <string name="screen_security_and_privacy_room_access_invite_only_option_description">"Seules les personnes invitées peuvent rejoindre."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Sur invitation uniquement"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Accès"</string> <string name="screen_security_and_privacy_room_access_section_header">"Accès"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Toute personne se trouvant dans un espace autorisé peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Toute personne de lespace %1$s peut joindre le salon."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string> <string name="screen_security_and_privacy_room_access_space_members_option_title">"Membres de lespace"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string> <string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Les Espaces ne sont pas encore supportés"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string> <string name="screen_security_and_privacy_room_address_section_footer">"Vous aurez besoin dune adresse pour le rendre visible dans lannuaire public."</string>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string> <string name="screen_edit_room_address_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_edit_room_address_title">"Adresa miestnosti"</string> <string name="screen_edit_room_address_title">"Upraviť adresu"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu miestnosti"</string> <string name="screen_security_and_privacy_add_room_address_action">"Pridať adresu"</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Ktokoľvek môže požiadať o pripojenie do miestnosti, ale správca alebo moderátor bude musieť žiadosť prijať."</string> <string name="screen_security_and_privacy_ask_to_join_option_description">"Všetci musia požiadať o prístup."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string> <string name="screen_security_and_privacy_ask_to_join_option_title">"Požiadať o pripojenie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string> <string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Áno, povoliť šifrovanie"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti. <string name="screen_security_and_privacy_enable_encryption_alert_description">"Po aktivácii nie je možné zakázať šifrovanie pre miestnosť. História správ bude viditeľná len pre členov miestnosti, odkedy boli pozvaní alebo keď vstúpili do miestnosti.
@@ -13,17 +13,22 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string> <string name="screen_security_and_privacy_encryption_section_footer">"Po zapnutí už šifrovanie nie je možné vypnúť."</string>
<string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string> <string name="screen_security_and_privacy_encryption_section_header">"Šifrovanie"</string>
<string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string> <string name="screen_security_and_privacy_encryption_toggle_title">"Povoliť end-to-end šifrovanie"</string>
<string name="screen_security_and_privacy_room_access_anyone_option_description">"Ktokoľvek môže nájsť a pripojiť sa"</string> <string name="screen_security_and_privacy_room_access_anyone_option_description">"Pripojiť sa môže ktokoľvek."</string>
<string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string> <string name="screen_security_and_privacy_room_access_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Ľudia sa môžu pripojiť len vtedy, ak sú pozvaní"</string> <string name="screen_security_and_privacy_room_access_footer">"Vyberte, ktorých členovia priestorov sa môžu pripojiť k tejto miestnosti bez pozvánky. %1$s"</string>
<string name="screen_security_and_privacy_room_access_footer_manage_spaces_action">"Spravovať priestory"</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_description">"Pripojiť sa môžu iba pozvaní ľudia."</string>
<string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string> <string name="screen_security_and_privacy_room_access_invite_only_option_title">"Iba na pozvánku"</string>
<string name="screen_security_and_privacy_room_access_section_header">"Prístup do miestnosti"</string> <string name="screen_security_and_privacy_room_access_section_header">"Prístup"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_multiple_parents_description">"Ktokoľvek v povolených priestoroch sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_single_parent_description">"Ktokoľvek v %1$s sa môže pripojiť."</string>
<string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string> <string name="screen_security_and_privacy_room_access_space_members_option_title">"Členovia priestoru"</string>
<string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string> <string name="screen_security_and_privacy_room_access_space_members_option_unavailable_description">"Priestory momentálne nie sú podporované"</string>
<string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu miestnosti, aby bola viditeľná v adresári."</string> <string name="screen_security_and_privacy_room_address_section_footer">"Budete potrebovať adresu, aby sa zobrazovala vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_address_section_header">"Adresa miestnosti"</string> <string name="screen_security_and_privacy_room_address_section_header">"Adresa"</string>
<string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string> <string name="screen_security_and_privacy_room_directory_visibility_section_footer">"Umožniť vyhľadanie tejto miestnosti v adresári verejných miestností %1$s"</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné v adresári verejných miestností"</string> <string name="screen_security_and_privacy_room_directory_visibility_toggle_description">"Umožniť nájdenie vyhľadávaním vo verejnom adresári."</string>
<string name="screen_security_and_privacy_room_directory_visibility_toggle_title">"Viditeľné vo verejnom adresári"</string>
<string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string> <string name="screen_security_and_privacy_room_history_anyone_option_title">"Ktokoľvek"</string>
<string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string> <string name="screen_security_and_privacy_room_history_section_header">"Kto môže čítať históriu"</string>
<string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string> <string name="screen_security_and_privacy_room_history_since_invite_option_title">"Len pre členov, odkedy boli pozvaní"</string>
@@ -31,6 +36,7 @@ To môže brániť správnemu fungovaniu robotov a premostení. Neodporúčame p
<string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými. <string name="screen_security_and_privacy_room_publishing_section_footer">"Adresy miestností predstavujú spôsoby, ako nájsť a získať prístup k miestnostiam. To tiež zaisťuje, že môžete jednoducho zdieľať svoju miestnosť s ostatnými.
Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string> Môžete sa rozhodnúť zverejniť svoju miestnosť v adresári verejných miestností vášho domovského servera."</string>
<string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string> <string name="screen_security_and_privacy_room_publishing_section_header">"Zverejnenie miestnosti"</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť miestnosti"</string> <string name="screen_security_and_privacy_room_visibility_section_footer">"Adresy sú spôsob, ako nájsť a získať prístup do miestností a priestorov. To tiež zabezpečuje, že ich môžete jednoducho zdieľať s ostatnými."</string>
<string name="screen_security_and_privacy_room_visibility_section_header">"Viditeľnosť"</string>
<string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string> <string name="screen_security_and_privacy_title">"Bezpečnosť a súkromie"</string>
</resources> </resources>

View File

@@ -3,8 +3,10 @@
<string name="screen_edit_room_address_room_address_section_footer">"Youll need an address in order to make it visible in the public directory."</string> <string name="screen_edit_room_address_room_address_section_footer">"Youll need an address in order to make it visible in the public directory."</string>
<string name="screen_edit_room_address_title">"Edit address"</string> <string name="screen_edit_room_address_title">"Edit address"</string>
<string name="screen_security_and_privacy_add_room_address_action">"Add address"</string> <string name="screen_security_and_privacy_add_room_address_action">"Add address"</string>
<string name="screen_security_and_privacy_ask_to_join_multiple_spaces_members_option_description">"Anyone in authorized spaces can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string> <string name="screen_security_and_privacy_ask_to_join_option_description">"Everyone must request access."</string>
<string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string> <string name="screen_security_and_privacy_ask_to_join_option_title">"Ask to join"</string>
<string name="screen_security_and_privacy_ask_to_join_single_space_members_option_description">"Anyone in %1$s can join, but everyone else must request access."</string>
<string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string> <string name="screen_security_and_privacy_enable_encryption_alert_confirm_button_title">"Yes, enable encryption"</string>
<string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room. <string name="screen_security_and_privacy_enable_encryption_alert_description">"Once enabled, encryption for a room cannot be disabled, Message history will only be visible for room members since they were invited or since they joined the room.
No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly. No one besides the room members will be able to read messages. This may prevent bots and bridges to work correctly.

View File

@@ -3,7 +3,7 @@
[versions] [versions]
# Project # Project
android_gradle_plugin = "8.13.1" android_gradle_plugin = "8.13.2"
# When updateing this, please also update the version in the file ./idea/kotlinc.xml # When updateing this, please also update the version in the file ./idea/kotlinc.xml
kotlin = "2.2.20" kotlin = "2.2.20"
kotlinpoet = "2.2.0" kotlinpoet = "2.2.0"
@@ -18,7 +18,7 @@ constraintlayout_compose = "1.1.1"
lifecycle = "2.9.2" lifecycle = "2.9.2"
activity = "1.11.0" activity = "1.11.0"
media3 = "1.8.0" media3 = "1.8.0"
camera = "1.5.1" camera = "1.5.2"
work = "2.11.0" work = "2.11.0"
# Compose # Compose
@@ -41,7 +41,7 @@ serialization_json = "1.9.0"
#other #other
coil = "3.3.0" coil = "3.3.0"
# Rollback to 1.0.4, 1.0.5 has this issue: https://github.com/airbnb/Showkase/issues/420 # Rollback to 1.0.4, 1.0.5 has this issue: https://github.com/airbnb/Showkase/issues/420
showkase = "1.0.4" showkase = "1.0.5"
appyx = "1.7.1" appyx = "1.7.1"
sqldelight = "2.2.1" sqldelight = "2.2.1"
wysiwyg = "2.40.0" wysiwyg = "2.40.0"
@@ -52,7 +52,7 @@ haze = "1.6.10"
dependencyAnalysis = "3.5.1" dependencyAnalysis = "3.5.1"
# DI # DI
metro = "0.8.1" metro = "0.8.2"
# Auto service # Auto service
autoservice = "1.1.1" autoservice = "1.1.1"
@@ -160,7 +160,7 @@ test_corektx = { module = "androidx.test:core-ktx", version.ref = "test_core" }
test_arch_core = "androidx.arch.core:core-testing:2.2.0" test_arch_core = "androidx.arch.core:core-testing:2.2.0"
test_junit = "junit:junit:4.13.2" test_junit = "junit:junit:4.13.2"
test_runner = "androidx.test:runner:1.7.0" test_runner = "androidx.test:runner:1.7.0"
test_mockk = "io.mockk:mockk:1.14.6" test_mockk = "io.mockk:mockk:1.14.7"
test_konsist = "com.lemonappdev:konsist:0.17.3" test_konsist = "com.lemonappdev:konsist:0.17.3"
test_turbine = "app.cash.turbine:turbine:1.2.1" test_turbine = "app.cash.turbine:turbine:1.2.1"
test_truth = "com.google.truth:truth:1.4.5" test_truth = "com.google.truth:truth:1.4.5"
@@ -177,7 +177,7 @@ test_detekt_test = { module = "io.gitlab.arturbosch.detekt:detekt-test", version
# https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt # https://github.com/matrix-org/matrix-rust-components-kotlin/commits/main/sdk/sdk-android/src/main/kotlin/org/matrix/rustcomponents/sdk/matrix_sdk_ffi.kt
# All new features should not be implemented in the pull request that upgrades the version, developers should # All new features should not be implemented in the pull request that upgrades the version, developers should
# only fix API breaks and may add some TODOs. # only fix API breaks and may add some TODOs.
matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.12.2" matrix_sdk = "org.matrix.rustcomponents:sdk-android:25.12.10"
# Others # Others
coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" }
@@ -199,14 +199,14 @@ matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose",
sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "app.cash.sqldelight:sqlite-driver", version.ref = "sqldelight" }
sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" } sqldelight-coroutines = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
sqlcipher = "net.zetetic:sqlcipher-android:4.11.0" sqlcipher = "net.zetetic:sqlcipher-android:4.12.0"
sqlite = "androidx.sqlite:sqlite-ktx:2.6.2" sqlite = "androidx.sqlite:sqlite-ktx:2.6.2"
unifiedpush = "org.unifiedpush.android:connector:3.1.2" unifiedpush = "org.unifiedpush.android:connector:3.1.2"
vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0" vanniktech_blurhash = "com.vanniktech:blurhash:0.3.0"
telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" } telephoto_zoomableimage = { module = "me.saket.telephoto:zoomable-image-coil", version.ref = "telephoto" }
telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" } telephoto_flick = { module = "me.saket.telephoto:flick-android", version.ref = "telephoto" }
statemachine = "com.freeletics.flowredux:compose:1.2.2" statemachine = "com.freeletics.flowredux:compose:1.2.2"
maplibre = "org.maplibre.gl:android-sdk:12.2.1" maplibre = "org.maplibre.gl:android-sdk:12.2.2"
maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2" maplibre_ktx = "org.maplibre.gl:android-sdk-ktx-v7:3.0.2"
maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2" maplibre_annotation = "org.maplibre.gl:android-plugin-annotation-v9:3.0.2"
opusencoder = "io.element.android:opusencoder:1.2.0" opusencoder = "io.element.android:opusencoder:1.2.0"
@@ -217,7 +217,7 @@ color_picker = "io.mhssn:colorpicker:1.0.0"
# Analytics # Analytics
posthog = "com.posthog:posthog-android:3.26.0" posthog = "com.posthog:posthog-android:3.26.0"
sentry = "io.sentry:sentry-android:8.27.1" sentry = "io.sentry:sentry-android:8.28.0"
# main branch can be tested replacing the version with main-SNAPSHOT # main branch can be tested replacing the version with main-SNAPSHOT
matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2" matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.29.2"

View File

@@ -60,7 +60,7 @@ fun Activity.openUrlInChromeCustomTab(
}) })
} }
.launchUrl(this, url.toUri()) .launchUrl(this, url.toUri())
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
openUrlInExternalApp(url) openUrlInExternalApp(url)
} }
} }

View File

@@ -20,7 +20,7 @@ fun String.hash() = try {
digest.digest() digest.digest()
.joinToString("") { String.format(Locale.ROOT, "%02X", it) } .joinToString("") { String.format(Locale.ROOT, "%02X", it) }
.lowercase(Locale.ROOT) .lowercase(Locale.ROOT)
} catch (exc: Exception) { } catch (_: Exception) {
// Should not happen, but just in case // Should not happen, but just in case
hashCode().toString() hashCode().toString()
} }

View File

@@ -32,7 +32,7 @@ fun Context.getApplicationLabel(packageName: String): String {
return try { return try {
val ai = packageManager.getApplicationInfoCompat(packageName, 0) val ai = packageManager.getApplicationInfoCompat(packageName, 0)
packageManager.getApplicationLabel(ai).toString() packageManager.getApplicationLabel(ai).toString()
} catch (e: PackageManager.NameNotFoundException) { } catch (_: PackageManager.NameNotFoundException) {
packageName packageName
} }
} }
@@ -96,7 +96,7 @@ fun Context.startNotificationSettingsIntent(
} else { } else {
startActivity(intent) startActivity(intent)
} }
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
toast(noActivityFoundMessage) toast(noActivityFoundMessage)
} }
} }
@@ -112,7 +112,7 @@ fun Context.openAppSettingsPage(
data = Uri.fromParts("package", packageName, null) data = Uri.fromParts("package", packageName, null)
} }
) )
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
toast(noActivityFoundMessage) toast(noActivityFoundMessage)
} }
} }
@@ -126,7 +126,7 @@ fun Context.startInstallFromSourceIntent(
.setData("package:$packageName".toUri()) .setData("package:$packageName".toUri())
try { try {
activityResultLauncher.launch(intent) activityResultLauncher.launch(intent)
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
toast(noActivityFoundMessage) toast(noActivityFoundMessage)
} }
} }
@@ -157,7 +157,7 @@ fun Context.startSharePlainTextIntent(
} else { } else {
startActivity(intent) startActivity(intent)
} }
} catch (activityNotFoundException: ActivityNotFoundException) { } catch (_: ActivityNotFoundException) {
toast(noActivityFoundMessage) toast(noActivityFoundMessage)
} }
} }

View File

@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.androidutils.text
import java.net.URLDecoder
import java.net.URLEncoder
import java.nio.charset.Charset
fun String.urlEncoded(charset: Charset = Charsets.UTF_8): String = URLEncoder.encode(this, charset.name())
fun String.urlDecoded(charset: Charset = Charsets.UTF_8): String = URLDecoder.decode(this, charset.name())

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,10 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
/** /**
* !!! WARNING !!! * !!! WARNING !!!
* *
@@ -14,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated
@@ -185,6 +181,9 @@ object CompoundIcons {
@Composable fun ErrorSolid(): ImageVector { @Composable fun ErrorSolid(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_error_solid) return ImageVector.vectorResource(R.drawable.ic_compound_error_solid)
} }
@Composable fun ExitFullScreen(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_exit_full_screen)
}
@Composable fun Expand(): ImageVector { @Composable fun Expand(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_expand) return ImageVector.vectorResource(R.drawable.ic_compound_expand)
} }
@@ -218,6 +217,9 @@ object CompoundIcons {
@Composable fun Forward(): ImageVector { @Composable fun Forward(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_forward) return ImageVector.vectorResource(R.drawable.ic_compound_forward)
} }
@Composable fun FullScreen(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_full_screen)
}
@Composable fun Grid(): ImageVector { @Composable fun Grid(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_grid) return ImageVector.vectorResource(R.drawable.ic_compound_grid)
} }
@@ -296,6 +298,9 @@ object CompoundIcons {
@Composable fun Leave(): ImageVector { @Composable fun Leave(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_leave) return ImageVector.vectorResource(R.drawable.ic_compound_leave)
} }
@Composable fun LeftPanelClose(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_left_panel_close)
}
@Composable fun Link(): ImageVector { @Composable fun Link(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_link) return ImageVector.vectorResource(R.drawable.ic_compound_link)
} }
@@ -503,6 +508,12 @@ object CompoundIcons {
@Composable fun SignOut(): ImageVector { @Composable fun SignOut(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_sign_out) return ImageVector.vectorResource(R.drawable.ic_compound_sign_out)
} }
@Composable fun Space(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_space)
}
@Composable fun SpaceSolid(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_space_solid)
}
@Composable fun Spinner(): ImageVector { @Composable fun Spinner(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_spinner) return ImageVector.vectorResource(R.drawable.ic_compound_spinner)
} }
@@ -620,12 +631,6 @@ object CompoundIcons {
@Composable fun Windows(): ImageVector { @Composable fun Windows(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_windows) return ImageVector.vectorResource(R.drawable.ic_compound_windows)
} }
@Composable fun Workspace(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_workspace)
}
@Composable fun WorkspaceSolid(): ImageVector {
return ImageVector.vectorResource(R.drawable.ic_compound_workspace_solid)
}
val all @Composable get() = persistentListOf<ImageVector>( val all @Composable get() = persistentListOf<ImageVector>(
Admin(), Admin(),
@@ -681,6 +686,7 @@ object CompoundIcons {
EndCall(), EndCall(),
Error(), Error(),
ErrorSolid(), ErrorSolid(),
ExitFullScreen(),
Expand(), Expand(),
Explore(), Explore(),
ExportArchive(), ExportArchive(),
@@ -692,6 +698,7 @@ object CompoundIcons {
Files(), Files(),
Filter(), Filter(),
Forward(), Forward(),
FullScreen(),
Grid(), Grid(),
Group(), Group(),
Guest(), Guest(),
@@ -718,6 +725,7 @@ object CompoundIcons {
Keyboard(), Keyboard(),
Labs(), Labs(),
Leave(), Leave(),
LeftPanelClose(),
Link(), Link(),
Linux(), Linux(),
ListBulleted(), ListBulleted(),
@@ -787,6 +795,8 @@ object CompoundIcons {
Shield(), Shield(),
Sidebar(), Sidebar(),
SignOut(), SignOut(),
Space(),
SpaceSolid(),
Spinner(), Spinner(),
Spotlight(), Spotlight(),
SpotlightView(), SpotlightView(),
@@ -826,8 +836,6 @@ object CompoundIcons {
Warning(), Warning(),
WebBrowser(), WebBrowser(),
Windows(), Windows(),
Workspace(),
WorkspaceSolid(),
) )
val allResIds get() = persistentListOf( val allResIds get() = persistentListOf(
@@ -884,6 +892,7 @@ object CompoundIcons {
R.drawable.ic_compound_end_call, R.drawable.ic_compound_end_call,
R.drawable.ic_compound_error, R.drawable.ic_compound_error,
R.drawable.ic_compound_error_solid, R.drawable.ic_compound_error_solid,
R.drawable.ic_compound_exit_full_screen,
R.drawable.ic_compound_expand, R.drawable.ic_compound_expand,
R.drawable.ic_compound_explore, R.drawable.ic_compound_explore,
R.drawable.ic_compound_export_archive, R.drawable.ic_compound_export_archive,
@@ -895,6 +904,7 @@ object CompoundIcons {
R.drawable.ic_compound_files, R.drawable.ic_compound_files,
R.drawable.ic_compound_filter, R.drawable.ic_compound_filter,
R.drawable.ic_compound_forward, R.drawable.ic_compound_forward,
R.drawable.ic_compound_full_screen,
R.drawable.ic_compound_grid, R.drawable.ic_compound_grid,
R.drawable.ic_compound_group, R.drawable.ic_compound_group,
R.drawable.ic_compound_guest, R.drawable.ic_compound_guest,
@@ -921,6 +931,7 @@ object CompoundIcons {
R.drawable.ic_compound_keyboard, R.drawable.ic_compound_keyboard,
R.drawable.ic_compound_labs, R.drawable.ic_compound_labs,
R.drawable.ic_compound_leave, R.drawable.ic_compound_leave,
R.drawable.ic_compound_left_panel_close,
R.drawable.ic_compound_link, R.drawable.ic_compound_link,
R.drawable.ic_compound_linux, R.drawable.ic_compound_linux,
R.drawable.ic_compound_list_bulleted, R.drawable.ic_compound_list_bulleted,
@@ -990,6 +1001,8 @@ object CompoundIcons {
R.drawable.ic_compound_shield, R.drawable.ic_compound_shield,
R.drawable.ic_compound_sidebar, R.drawable.ic_compound_sidebar,
R.drawable.ic_compound_sign_out, R.drawable.ic_compound_sign_out,
R.drawable.ic_compound_space,
R.drawable.ic_compound_space_solid,
R.drawable.ic_compound_spinner, R.drawable.ic_compound_spinner,
R.drawable.ic_compound_spotlight, R.drawable.ic_compound_spotlight,
R.drawable.ic_compound_spotlight_view, R.drawable.ic_compound_spotlight_view,
@@ -1029,7 +1042,5 @@ object CompoundIcons {
R.drawable.ic_compound_warning, R.drawable.ic_compound_warning,
R.drawable.ic_compound_web_browser, R.drawable.ic_compound_web_browser,
R.drawable.ic_compound_windows, R.drawable.ic_compound_windows,
R.drawable.ic_compound_workspace,
R.drawable.ic_compound_workspace_solid,
) )
} }

View File

@@ -1,19 +1,10 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
@file:Suppress("all")
package io.element.android.compound.tokens.generated
import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.Color
/** /**
* !!! WARNING !!! * !!! WARNING !!!
* *
@@ -21,12 +12,14 @@ import androidx.compose.ui.graphics.Color
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all")
package io.element.android.compound.tokens.generated
import androidx.compose.ui.graphics.Color
/** /**
* This class holds all the semantic tokens of the Compound theme. * This class holds all the semantic tokens of the Compound theme.
*/ */
data class SemanticColors( data class SemanticColors(
/** Background colour for accent or brand actions. State: Hover */ /** Background colour for accent or brand actions. State: Hover */
val bgAccentHovered: Color, val bgAccentHovered: Color,
@@ -50,17 +43,21 @@ data class SemanticColors(
val bgActionSecondaryPressed: Color, val bgActionSecondaryPressed: Color,
/** Background colour for secondary actions. State: Rest. */ /** Background colour for secondary actions. State: Rest. */
val bgActionSecondaryRest: Color, val bgActionSecondaryRest: Color,
/** Background colour for tertiary actions. State: Hover */
val bgActionTertiaryHovered: Color,
/** Background colour for tertiary actions. State: Rest */
val bgActionTertiaryRest: Color,
/** Background colour for tertiary actions. State: Selected */
val bgActionTertiarySelected: Color,
/** Badge accent background colour */ /** Badge accent background colour */
val bgBadgeAccent: Color, val bgBadgeAccent: Color,
/** Badge default background colour */ /** Badge default background colour */
val bgBadgeDefault: Color, val bgBadgeDefault: Color,
/** Badge info background colour */ /** Badge info background colour */
val bgBadgeInfo: Color, val bgBadgeInfo: Color,
/** Default global background for the user interface. /** Default global background for the user interface. Elevation: Default (Level 0) */
Elevation: Default (Level 0) */
val bgCanvasDefault: Color, val bgCanvasDefault: Color,
/** Default global background for the user interface. /** Default global background for the user interface. Elevation: Level 1. */
Elevation: Level 1. */
val bgCanvasDefaultLevel1: Color, val bgCanvasDefaultLevel1: Color,
/** Default background for disabled elements. There's no minimum contrast requirement. */ /** Default background for disabled elements. There's no minimum contrast requirement. */
val bgCanvasDisabled: Color, val bgCanvasDisabled: Color,
@@ -86,14 +83,11 @@ Elevation: Level 1. */
val bgDecorative6: Color, val bgDecorative6: Color,
/** Subtle background colour for informational elements. State: Rest. */ /** Subtle background colour for informational elements. State: Rest. */
val bgInfoSubtle: Color, val bgInfoSubtle: Color,
/** Medium contrast surfaces. /** Medium contrast surfaces. Elevation: Default (Level 2). */
Elevation: Default (Level 2). */
val bgSubtlePrimary: Color, val bgSubtlePrimary: Color,
/** Low contrast surfaces. /** Low contrast surfaces. Elevation: Default (Level 1). */
Elevation: Default (Level 1). */
val bgSubtleSecondary: Color, val bgSubtleSecondary: Color,
/** Lower contrast surfaces. /** Lower contrast surfaces. Elevation: Level 0. */
Elevation: Level 0. */
val bgSubtleSecondaryLevel0: Color, val bgSubtleSecondaryLevel0: Color,
/** Subtle background colour for success state elements. State: Rest. */ /** Subtle background colour for success state elements. State: Rest. */
val bgSuccessSubtle: Color, val bgSuccessSubtle: Color,

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated
@@ -37,9 +34,12 @@ val compoundColorsDark = SemanticColors(
bgActionSecondaryHovered = DarkColorTokens.colorAlphaGray200, bgActionSecondaryHovered = DarkColorTokens.colorAlphaGray200,
bgActionSecondaryPressed = DarkColorTokens.colorAlphaGray300, bgActionSecondaryPressed = DarkColorTokens.colorAlphaGray300,
bgActionSecondaryRest = DarkColorTokens.colorThemeBg, bgActionSecondaryRest = DarkColorTokens.colorThemeBg,
bgBadgeAccent = DarkColorTokens.colorAlphaGreen300, bgActionTertiaryHovered = DarkColorTokens.colorGray300,
bgBadgeDefault = DarkColorTokens.colorAlphaGray300, bgActionTertiaryRest = DarkColorTokens.colorThemeBg,
bgBadgeInfo = DarkColorTokens.colorAlphaBlue300, bgActionTertiarySelected = DarkColorTokens.colorGray400,
bgBadgeAccent = DarkColorTokens.colorAlphaGreen500,
bgBadgeDefault = DarkColorTokens.colorAlphaGray500,
bgBadgeInfo = DarkColorTokens.colorAlphaBlue500,
bgCanvasDefault = DarkColorTokens.colorThemeBg, bgCanvasDefault = DarkColorTokens.colorThemeBg,
bgCanvasDefaultLevel1 = DarkColorTokens.colorGray300, bgCanvasDefaultLevel1 = DarkColorTokens.colorGray300,
bgCanvasDisabled = DarkColorTokens.colorGray200, bgCanvasDisabled = DarkColorTokens.colorGray200,
@@ -89,7 +89,7 @@ val compoundColorsDark = SemanticColors(
iconAccentTertiary = DarkColorTokens.colorGreen800, iconAccentTertiary = DarkColorTokens.colorGreen800,
iconCriticalPrimary = DarkColorTokens.colorRed900, iconCriticalPrimary = DarkColorTokens.colorRed900,
iconDisabled = DarkColorTokens.colorGray700, iconDisabled = DarkColorTokens.colorGray700,
iconInfoPrimary = DarkColorTokens.colorBlue900, iconInfoPrimary = DarkColorTokens.colorBlue1100,
iconOnSolidPrimary = DarkColorTokens.colorThemeBg, iconOnSolidPrimary = DarkColorTokens.colorThemeBg,
iconPrimary = DarkColorTokens.colorGray1400, iconPrimary = DarkColorTokens.colorGray1400,
iconPrimaryAlpha = DarkColorTokens.colorAlphaGray1400, iconPrimaryAlpha = DarkColorTokens.colorAlphaGray1400,
@@ -112,8 +112,8 @@ val compoundColorsDark = SemanticColors(
textDecorative5 = DarkColorTokens.colorPink1100, textDecorative5 = DarkColorTokens.colorPink1100,
textDecorative6 = DarkColorTokens.colorOrange1100, textDecorative6 = DarkColorTokens.colorOrange1100,
textDisabled = DarkColorTokens.colorGray800, textDisabled = DarkColorTokens.colorGray800,
textInfoPrimary = DarkColorTokens.colorBlue900, textInfoPrimary = DarkColorTokens.colorBlue1100,
textLinkExternal = DarkColorTokens.colorBlue900, textLinkExternal = DarkColorTokens.colorBlue1100,
textOnSolidPrimary = DarkColorTokens.colorThemeBg, textOnSolidPrimary = DarkColorTokens.colorThemeBg,
textPrimary = DarkColorTokens.colorGray1400, textPrimary = DarkColorTokens.colorGray1400,
textSecondary = DarkColorTokens.colorGray900, textSecondary = DarkColorTokens.colorGray900,

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated
@@ -37,9 +34,12 @@ val compoundColorsHcDark = SemanticColors(
bgActionSecondaryHovered = DarkHcColorTokens.colorAlphaGray200, bgActionSecondaryHovered = DarkHcColorTokens.colorAlphaGray200,
bgActionSecondaryPressed = DarkHcColorTokens.colorAlphaGray300, bgActionSecondaryPressed = DarkHcColorTokens.colorAlphaGray300,
bgActionSecondaryRest = DarkHcColorTokens.colorThemeBg, bgActionSecondaryRest = DarkHcColorTokens.colorThemeBg,
bgBadgeAccent = DarkHcColorTokens.colorAlphaGreen300, bgActionTertiaryHovered = DarkHcColorTokens.colorGray300,
bgBadgeDefault = DarkHcColorTokens.colorAlphaGray300, bgActionTertiaryRest = DarkHcColorTokens.colorThemeBg,
bgBadgeInfo = DarkHcColorTokens.colorAlphaBlue300, bgActionTertiarySelected = DarkHcColorTokens.colorGray400,
bgBadgeAccent = DarkHcColorTokens.colorAlphaGreen500,
bgBadgeDefault = DarkHcColorTokens.colorAlphaGray500,
bgBadgeInfo = DarkHcColorTokens.colorAlphaBlue500,
bgCanvasDefault = DarkHcColorTokens.colorThemeBg, bgCanvasDefault = DarkHcColorTokens.colorThemeBg,
bgCanvasDefaultLevel1 = DarkHcColorTokens.colorGray300, bgCanvasDefaultLevel1 = DarkHcColorTokens.colorGray300,
bgCanvasDisabled = DarkHcColorTokens.colorGray200, bgCanvasDisabled = DarkHcColorTokens.colorGray200,
@@ -89,7 +89,7 @@ val compoundColorsHcDark = SemanticColors(
iconAccentTertiary = DarkHcColorTokens.colorGreen800, iconAccentTertiary = DarkHcColorTokens.colorGreen800,
iconCriticalPrimary = DarkHcColorTokens.colorRed900, iconCriticalPrimary = DarkHcColorTokens.colorRed900,
iconDisabled = DarkHcColorTokens.colorGray700, iconDisabled = DarkHcColorTokens.colorGray700,
iconInfoPrimary = DarkHcColorTokens.colorBlue900, iconInfoPrimary = DarkHcColorTokens.colorBlue1100,
iconOnSolidPrimary = DarkHcColorTokens.colorThemeBg, iconOnSolidPrimary = DarkHcColorTokens.colorThemeBg,
iconPrimary = DarkHcColorTokens.colorGray1400, iconPrimary = DarkHcColorTokens.colorGray1400,
iconPrimaryAlpha = DarkHcColorTokens.colorAlphaGray1400, iconPrimaryAlpha = DarkHcColorTokens.colorAlphaGray1400,
@@ -112,8 +112,8 @@ val compoundColorsHcDark = SemanticColors(
textDecorative5 = DarkHcColorTokens.colorPink1100, textDecorative5 = DarkHcColorTokens.colorPink1100,
textDecorative6 = DarkHcColorTokens.colorOrange1100, textDecorative6 = DarkHcColorTokens.colorOrange1100,
textDisabled = DarkHcColorTokens.colorGray800, textDisabled = DarkHcColorTokens.colorGray800,
textInfoPrimary = DarkHcColorTokens.colorBlue900, textInfoPrimary = DarkHcColorTokens.colorBlue1100,
textLinkExternal = DarkHcColorTokens.colorBlue900, textLinkExternal = DarkHcColorTokens.colorBlue1100,
textOnSolidPrimary = DarkHcColorTokens.colorThemeBg, textOnSolidPrimary = DarkHcColorTokens.colorThemeBg,
textPrimary = DarkHcColorTokens.colorGray1400, textPrimary = DarkHcColorTokens.colorGray1400,
textSecondary = DarkHcColorTokens.colorGray900, textSecondary = DarkHcColorTokens.colorGray900,

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated
@@ -37,9 +34,12 @@ val compoundColorsLight = SemanticColors(
bgActionSecondaryHovered = LightColorTokens.colorAlphaGray200, bgActionSecondaryHovered = LightColorTokens.colorAlphaGray200,
bgActionSecondaryPressed = LightColorTokens.colorAlphaGray300, bgActionSecondaryPressed = LightColorTokens.colorAlphaGray300,
bgActionSecondaryRest = LightColorTokens.colorThemeBg, bgActionSecondaryRest = LightColorTokens.colorThemeBg,
bgBadgeAccent = LightColorTokens.colorAlphaGreen300, bgActionTertiaryHovered = LightColorTokens.colorGray300,
bgBadgeDefault = LightColorTokens.colorAlphaGray300, bgActionTertiaryRest = LightColorTokens.colorThemeBg,
bgBadgeInfo = LightColorTokens.colorAlphaBlue300, bgActionTertiarySelected = LightColorTokens.colorGray400,
bgBadgeAccent = LightColorTokens.colorAlphaGreen400,
bgBadgeDefault = LightColorTokens.colorAlphaGray400,
bgBadgeInfo = LightColorTokens.colorAlphaBlue400,
bgCanvasDefault = LightColorTokens.colorThemeBg, bgCanvasDefault = LightColorTokens.colorThemeBg,
bgCanvasDefaultLevel1 = LightColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = LightColorTokens.colorThemeBg,
bgCanvasDisabled = LightColorTokens.colorGray200, bgCanvasDisabled = LightColorTokens.colorGray200,
@@ -89,7 +89,7 @@ val compoundColorsLight = SemanticColors(
iconAccentTertiary = LightColorTokens.colorGreen800, iconAccentTertiary = LightColorTokens.colorGreen800,
iconCriticalPrimary = LightColorTokens.colorRed900, iconCriticalPrimary = LightColorTokens.colorRed900,
iconDisabled = LightColorTokens.colorGray700, iconDisabled = LightColorTokens.colorGray700,
iconInfoPrimary = LightColorTokens.colorBlue900, iconInfoPrimary = LightColorTokens.colorBlue1100,
iconOnSolidPrimary = LightColorTokens.colorThemeBg, iconOnSolidPrimary = LightColorTokens.colorThemeBg,
iconPrimary = LightColorTokens.colorGray1400, iconPrimary = LightColorTokens.colorGray1400,
iconPrimaryAlpha = LightColorTokens.colorAlphaGray1400, iconPrimaryAlpha = LightColorTokens.colorAlphaGray1400,
@@ -112,8 +112,8 @@ val compoundColorsLight = SemanticColors(
textDecorative5 = LightColorTokens.colorPink1100, textDecorative5 = LightColorTokens.colorPink1100,
textDecorative6 = LightColorTokens.colorOrange1100, textDecorative6 = LightColorTokens.colorOrange1100,
textDisabled = LightColorTokens.colorGray800, textDisabled = LightColorTokens.colorGray800,
textInfoPrimary = LightColorTokens.colorBlue900, textInfoPrimary = LightColorTokens.colorBlue1100,
textLinkExternal = LightColorTokens.colorBlue900, textLinkExternal = LightColorTokens.colorBlue1100,
textOnSolidPrimary = LightColorTokens.colorThemeBg, textOnSolidPrimary = LightColorTokens.colorThemeBg,
textPrimary = LightColorTokens.colorGray1400, textPrimary = LightColorTokens.colorGray1400,
textSecondary = LightColorTokens.colorGray900, textSecondary = LightColorTokens.colorGray900,

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated
@@ -37,9 +34,12 @@ val compoundColorsHcLight = SemanticColors(
bgActionSecondaryHovered = LightHcColorTokens.colorAlphaGray200, bgActionSecondaryHovered = LightHcColorTokens.colorAlphaGray200,
bgActionSecondaryPressed = LightHcColorTokens.colorAlphaGray300, bgActionSecondaryPressed = LightHcColorTokens.colorAlphaGray300,
bgActionSecondaryRest = LightHcColorTokens.colorThemeBg, bgActionSecondaryRest = LightHcColorTokens.colorThemeBg,
bgBadgeAccent = LightHcColorTokens.colorAlphaGreen300, bgActionTertiaryHovered = LightHcColorTokens.colorGray300,
bgBadgeDefault = LightHcColorTokens.colorAlphaGray300, bgActionTertiaryRest = LightHcColorTokens.colorThemeBg,
bgBadgeInfo = LightHcColorTokens.colorAlphaBlue300, bgActionTertiarySelected = LightHcColorTokens.colorGray400,
bgBadgeAccent = LightHcColorTokens.colorAlphaGreen400,
bgBadgeDefault = LightHcColorTokens.colorAlphaGray400,
bgBadgeInfo = LightHcColorTokens.colorAlphaBlue400,
bgCanvasDefault = LightHcColorTokens.colorThemeBg, bgCanvasDefault = LightHcColorTokens.colorThemeBg,
bgCanvasDefaultLevel1 = LightHcColorTokens.colorThemeBg, bgCanvasDefaultLevel1 = LightHcColorTokens.colorThemeBg,
bgCanvasDisabled = LightHcColorTokens.colorGray200, bgCanvasDisabled = LightHcColorTokens.colorGray200,
@@ -89,7 +89,7 @@ val compoundColorsHcLight = SemanticColors(
iconAccentTertiary = LightHcColorTokens.colorGreen800, iconAccentTertiary = LightHcColorTokens.colorGreen800,
iconCriticalPrimary = LightHcColorTokens.colorRed900, iconCriticalPrimary = LightHcColorTokens.colorRed900,
iconDisabled = LightHcColorTokens.colorGray700, iconDisabled = LightHcColorTokens.colorGray700,
iconInfoPrimary = LightHcColorTokens.colorBlue900, iconInfoPrimary = LightHcColorTokens.colorBlue1100,
iconOnSolidPrimary = LightHcColorTokens.colorThemeBg, iconOnSolidPrimary = LightHcColorTokens.colorThemeBg,
iconPrimary = LightHcColorTokens.colorGray1400, iconPrimary = LightHcColorTokens.colorGray1400,
iconPrimaryAlpha = LightHcColorTokens.colorAlphaGray1400, iconPrimaryAlpha = LightHcColorTokens.colorAlphaGray1400,
@@ -112,8 +112,8 @@ val compoundColorsHcLight = SemanticColors(
textDecorative5 = LightHcColorTokens.colorPink1100, textDecorative5 = LightHcColorTokens.colorPink1100,
textDecorative6 = LightHcColorTokens.colorOrange1100, textDecorative6 = LightHcColorTokens.colorOrange1100,
textDisabled = LightHcColorTokens.colorGray800, textDisabled = LightHcColorTokens.colorGray800,
textInfoPrimary = LightHcColorTokens.colorBlue900, textInfoPrimary = LightHcColorTokens.colorBlue1100,
textLinkExternal = LightHcColorTokens.colorBlue900, textLinkExternal = LightHcColorTokens.colorBlue1100,
textOnSolidPrimary = LightHcColorTokens.colorThemeBg, textOnSolidPrimary = LightHcColorTokens.colorThemeBg,
textPrimary = LightHcColorTokens.colorGray1400, textPrimary = LightHcColorTokens.colorGray1400,
textSecondary = LightHcColorTokens.colorGray900, textSecondary = LightHcColorTokens.colorGray900,

View File

@@ -1,12 +1,10 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
*/ */
/** /**
* !!! WARNING !!! * !!! WARNING !!!
* *
@@ -14,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated package io.element.android.compound.tokens.generated

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated.internal package io.element.android.compound.tokens.generated.internal

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated.internal package io.element.android.compound.tokens.generated.internal

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated.internal package io.element.android.compound.tokens.generated.internal

View File

@@ -1,6 +1,5 @@
/* /*
* Copyright (c) 2025 Element Creations Ltd. * Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
* *
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial. * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details. * Please see LICENSE files in the repository root for full details.
@@ -13,8 +12,6 @@
* DO NOT EDIT MANUALLY. * DO NOT EDIT MANUALLY.
*/ */
@file:Suppress("all") @file:Suppress("all")
package io.element.android.compound.tokens.generated.internal package io.element.android.compound.tokens.generated.internal

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M10,20a1,1 0,1 1,-2 0v-4L4,16a1,1 0,1 1,0 -2h6zM20,14a1,1 0,1 1,0 2h-4v4a1,1 0,1 1,-2 0v-6zM9,3a1,1 0,0 1,1 1v6L4,10a1,1 0,0 1,0 -2h4L8,4a1,1 0,0 1,1 -1m6,0a1,1 0,0 1,1 1v4h4a1,1 0,1 1,0 2h-6L14,4a1,1 0,0 1,1 -1"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M4,14a1,1 0,0 1,1 1v4h4a1,1 0,1 1,0 2L3,21v-6a1,1 0,0 1,1 -1m16,0a1,1 0,0 1,1 1v6h-6a1,1 0,1 1,0 -2h4v-4a1,1 0,0 1,1 -1M9,3a1,1 0,0 1,0 2L5,5v4a1,1 0,0 1,-2 0L3,3zM21,9a1,1 0,1 1,-2 0L19,5h-4a1,1 0,1 1,0 -2h6z"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M16.5,14.8L16.5,9.2q0,-0.35 -0.3,-0.475t-0.55,0.125L13.2,11.3q-0.3,0.3 -0.3,0.7t0.3,0.7l2.45,2.45q0.25,0.25 0.55,0.125t0.3,-0.475M5,19q-0.824,0 -1.412,-0.587A1.93,1.93 0,0 1,3 17L3,7q0,-0.824 0.587,-1.412A1.93,1.93 0,0 1,5 5h14q0.824,0 1.413,0.588Q21,6.175 21,7v10q0,0.824 -0.587,1.413A1.93,1.93 0,0 1,19 19zM8,17L8,7L5,7v10zM10,17h9L19,7h-9z"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,21q-1.65,0 -2.825,-1.175T2,17t1.175,-2.825T6,13t2.825,1.175T10,17t-1.175,2.825T6,21m12,0q-1.65,0 -2.825,-1.175T14,17t1.175,-2.825T18,13t2.825,1.175T22,17t-1.175,2.825T18,21M6,19q0.824,0 1.412,-0.587Q8,17.825 8,17t-0.588,-1.412A1.93,1.93 0,0 0,6 15q-0.824,0 -1.412,0.588A1.93,1.93 0,0 0,4 17q0,0.824 0.588,1.413Q5.175,19 6,19m12,0q0.824,0 1.413,-0.587Q20,17.825 20,17t-0.587,-1.412A1.93,1.93 0,0 0,18 15q-0.824,0 -1.413,0.588A1.93,1.93 0,0 0,16 17q0,0.824 0.587,1.413Q17.176,19 18,19m-6,-8q-1.65,0 -2.825,-1.175T8,7t1.175,-2.825T12,3t2.825,1.175T16,7t-1.175,2.825T12,11m0,-2q0.825,0 1.412,-0.588Q14,7.826 14,7q0,-0.824 -0.588,-1.412A1.93,1.93 0,0 0,12 5q-0.825,0 -1.412,0.588A1.93,1.93 0,0 0,10 7q0,0.824 0.588,1.412Q11.175,9 12,9"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3.175,19.825Q4.35,21 6,21t2.825,-1.175T10,17t-1.175,-2.825T6,13t-2.825,1.175T2,17t1.175,2.825m12,0Q16.35,21 18,21t2.825,-1.175T22,17t-1.175,-2.825T18,13t-2.825,1.175T14,17t1.175,2.825m-6,-10Q10.35,11 12,11t2.825,-1.175T16,7t-1.175,-2.825T12,3 9.175,4.175 8,7t1.175,2.825"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -4,10 +4,10 @@
android:autoMirrored="true" android:autoMirrored="true"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#FF000000" android:pathData="M7,10a0.97,0.97 0,0 1,-0.713 -0.287A0.97,0.97 0,0 1,6 9q0,-0.424 0.287,-0.713A0.97,0.97 0,0 1,7 8h10q0.424,0 0.712,0.287Q18,8.576 18,9t-0.288,0.713A0.97,0.97 0,0 1,17 10zM7,14a0.97,0.97 0,0 1,-0.713 -0.287A0.97,0.97 0,0 1,6 13q0,-0.424 0.287,-0.713A0.97,0.97 0,0 1,7 12h6q0.424,0 0.713,0.287 0.287,0.288 0.287,0.713 0,0.424 -0.287,0.713A0.97,0.97 0,0 1,13 14z"
android:pathData="M7 10a0.97 0.97 0 0 1-0.71-0.29A0.97 0.97 0 0 1 6 9q0-0.42 0.29-0.71A0.97 0.97 0 0 1 7 8h10q0.42 0 0.71 0.29T18 9t-0.29 0.71A0.97 0.97 0 0 1 17 10z m0 4a0.97 0.97 0 0 1-0.71-0.29A0.97 0.97 0 0 1 6 13q0-0.42 0.29-0.71A0.97 0.97 0 0 1 7 12h6q0.42 0 0.71 0.29T14 13q0 0.42-0.29 0.71A0.97 0.97 0 0 1 13 14z"/> android:fillColor="#FF000000"/>
<path <path
android:fillColor="#FF000000" android:pathData="M3.707,21.293c-0.63,0.63 -1.707,0.184 -1.707,-0.707V5a2,2 0,0 1,2 -2h16a2,2 0,0 1,2 2v12a2,2 0,0 1,-2 2H6zM6,17h14V5H4v13.172l0.586,-0.586A2,2 0,0 1,6 17"
android:pathData="M3.7 21.3C3.09 21.91 2 21.47 2 20.58V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6zM6 17h14V5H4v13.17l0.59-0.58A2 2 0 0 1 6 17"/> android:fillColor="#FF000000"/>
</vector> </vector>

View File

@@ -4,7 +4,7 @@
android:autoMirrored="true" android:autoMirrored="true"
android:viewportWidth="24" android:viewportWidth="24"
android:viewportHeight="24"> android:viewportHeight="24">
<path <path
android:fillColor="#FF000000" android:pathData="M4,3h16a2,2 0,0 1,2 2v12a2,2 0,0 1,-2 2H6l-2.293,2.293c-0.63,0.63 -1.707,0.184 -1.707,-0.707V5a2,2 0,0 1,2 -2m3,7h10q0.424,0 0.712,-0.287A0.97,0.97 0,0 0,18 9a0.97,0.97 0,0 0,-0.288 -0.713A0.97,0.97 0,0 0,17 8H7a0.97,0.97 0,0 0,-0.713 0.287A0.97,0.97 0,0 0,6 9q0,0.424 0.287,0.713Q6.576,10 7,10m0,4h6q0.424,0 0.713,-0.287A0.97,0.97 0,0 0,14 13a0.97,0.97 0,0 0,-0.287 -0.713A0.97,0.97 0,0 0,13 12H7a0.97,0.97 0,0 0,-0.713 0.287A0.97,0.97 0,0 0,6 13q0,0.424 0.287,0.713Q6.576,14 7,14"
android:pathData="M4 3h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H6l-2.3 2.3C3.09 21.91 2 21.47 2 20.58V5a2 2 0 0 1 2-2m3 7h10q0.42 0 0.71-0.29A0.97 0.97 0 0 0 18 9a0.97 0.97 0 0 0-0.29-0.71A0.97 0.97 0 0 0 17 8H7a0.97 0.97 0 0 0-0.71 0.29A0.97 0.97 0 0 0 6 9q0 0.42 0.29 0.71T7 10m0 4h6q0.42 0 0.71-0.29A0.97 0.97 0 0 0 14 13a0.97 0.97 0 0 0-0.29-0.71A0.97 0.97 0 0 0 13 12H7a0.97 0.97 0 0 0-0.71 0.29A0.97 0.97 0 0 0 6 13q0 0.42 0.29 0.71T7 14"/> android:fillColor="#FF000000"/>
</vector> </vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M6,21q-1.65,0 -2.825,-1.175T2,17t1.175,-2.825T6,13t2.825,1.175T10,17t-1.175,2.825T6,21m12,0q-1.65,0 -2.825,-1.175T14,17t1.175,-2.825T18,13t2.825,1.175T22,17t-1.175,2.825T18,21M6,19q0.824,0 1.412,-0.587Q8,17.825 8,17t-0.588,-1.412A1.93,1.93 0,0 0,6 15q-0.824,0 -1.412,0.588A1.93,1.93 0,0 0,4 17q0,0.824 0.588,1.413Q5.175,19 6,19m12,0q0.824,0 1.413,-0.587Q20,17.825 20,17t-0.587,-1.412A1.93,1.93 0,0 0,18 15q-0.824,0 -1.413,0.588A1.93,1.93 0,0 0,16 17q0,0.824 0.587,1.413Q17.176,19 18,19m-6,-8q-1.65,0 -2.825,-1.175T8,7t1.175,-2.825T12,3t2.825,1.175T16,7t-1.175,2.825T12,11m0,-2q0.825,0 1.412,-0.588Q14,7.826 14,7q0,-0.824 -0.588,-1.412A1.93,1.93 0,0 0,12 5q-0.825,0 -1.412,0.588A1.93,1.93 0,0 0,10 7q0,0.824 0.588,1.412Q11.175,9 12,9"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M3.175,19.825Q4.35,21 6,21t2.825,-1.175T10,17t-1.175,-2.825T6,13t-2.825,1.175T2,17t1.175,2.825m12,0Q16.35,21 18,21t2.825,-1.175T22,17t-1.175,-2.825T18,13t-2.825,1.175T14,17t1.175,2.825m-6,-10Q10.35,11 12,11t2.825,-1.175T16,7t-1.175,-2.825T12,3 9.175,4.175 8,7t1.175,2.825"
android:fillColor="#FF000000"/>
</vector>

View File

@@ -21,7 +21,7 @@ fun String.md5() = try {
digest.digest() digest.digest()
.joinToString("") { String.format(locale, "%02X", it) } .joinToString("") { String.format(locale, "%02X", it) }
.lowercase(locale) .lowercase(locale)
} catch (exc: Exception) { } catch (_: Exception) {
// Should not happen, but just in case // Should not happen, but just in case
hashCode().toString() hashCode().toString()
} }

View File

@@ -14,7 +14,7 @@ fun String.isValidUrl(): Boolean {
return try { return try {
URI(this).toURL() URI(this).toURL()
true true
} catch (t: Throwable) { } catch (_: Throwable) {
false false
} }
} }

View File

@@ -10,6 +10,7 @@ package io.element.android.libraries.deeplink.impl
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.text.urlEncoded
import io.element.android.libraries.deeplink.api.DeepLinkCreator import io.element.android.libraries.deeplink.api.DeepLinkCreator
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@@ -21,13 +22,13 @@ class DefaultDeepLinkCreator : DeepLinkCreator {
override fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, eventId: EventId?): String { override fun create(sessionId: SessionId, roomId: RoomId?, threadId: ThreadId?, eventId: EventId?): String {
return buildString { return buildString {
append("$SCHEME://$HOST/") append("$SCHEME://$HOST/")
append(sessionId.value) append(sessionId.value.urlEncoded())
append("/") append("/")
append(roomId?.value.orEmpty()) append(roomId?.value?.urlEncoded().orEmpty())
append("/") append("/")
append(threadId?.value.orEmpty()) append(threadId?.value?.urlEncoded().orEmpty())
append("/") append("/")
append(eventId?.value.orEmpty()) append(eventId?.value?.urlEncoded().orEmpty())
} }
// Remove all possible trailing '/' characters: // Remove all possible trailing '/' characters:
// No event id // No event id

View File

@@ -12,6 +12,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.AppScope
import dev.zacsweers.metro.ContributesBinding import dev.zacsweers.metro.ContributesBinding
import io.element.android.libraries.androidutils.text.urlDecoded
import io.element.android.libraries.deeplink.api.DeeplinkData import io.element.android.libraries.deeplink.api.DeeplinkData
import io.element.android.libraries.deeplink.api.DeeplinkParser import io.element.android.libraries.deeplink.api.DeeplinkParser
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
@@ -31,7 +32,7 @@ class DefaultDeeplinkParser : DeeplinkParser {
private fun Uri.toDeeplinkData(): DeeplinkData? { private fun Uri.toDeeplinkData(): DeeplinkData? {
if (scheme != SCHEME) return null if (scheme != SCHEME) return null
if (host != HOST) return null if (host != HOST) return null
val pathBits = path.orEmpty().split("/").drop(1) val pathBits = encodedPath.orEmpty().split("/").drop(1).map { it.urlDecoded() }
val sessionId = pathBits.elementAtOrNull(0)?.let(::SessionId) ?: return null val sessionId = pathBits.elementAtOrNull(0)?.let(::SessionId) ?: return null
return when (val screenPathComponent = pathBits.elementAtOrNull(1)) { return when (val screenPathComponent = pathBits.elementAtOrNull(1)) {

Some files were not shown because too many files have changed in this diff Show More