Merge branch 'release/25.03.4'

This commit is contained in:
Jorge Martín 2025-03-27 11:26:00 +01:00
commit e26c118c29
12 changed files with 155 additions and 85 deletions

View file

@ -1,3 +1,34 @@
Changes in Element X v25.03.3
=============================
<!-- Release notes generated using configuration in .github/release.yml at v25.03.3 -->
## What's Changed
### ✨ Features
* Add 'unencrypted room' badges and labels by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4445
* Use embedded version of Element Call by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4470
### 🐛 Bugfixes
* Fix 'unverified session' flow displayed when creating account by @jmartinesp in https://github.com/element-hq/element-x-android/pull/4467
### 🗣 Translations
* Sync Strings by @ElementBot in https://github.com/element-hq/element-x-android/pull/4461
### 🧱 Build
* Let element enterprise be able to configure id for mapTiler. by @bmarty in https://github.com/element-hq/element-x-android/pull/4446
### Dependency upgrades
* chore(deps): update rnkdsh/action-upload-diawi action to v1.5.8 by @renovate in https://github.com/element-hq/element-x-android/pull/4457
* chore(deps): update plugin licensee to v1.13.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4447
* fix(deps): update dependency org.maplibre.gl:android-sdk to v11.8.4 by @renovate in https://github.com/element-hq/element-x-android/pull/4450
* fix(deps): update dependency com.google.firebase:firebase-bom to v33.11.0 by @renovate in https://github.com/element-hq/element-x-android/pull/4448
* fix(deps): update dependency org.matrix.rustcomponents:sdk-android to v25.3.24 by @renovate in https://github.com/element-hq/element-x-android/pull/4394
* fix(deps): update dependencyanalysis to v2.13.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4464
* chore(deps): update plugin sonarqube to v6.1.0.5360 by @renovate in https://github.com/element-hq/element-x-android/pull/4468
* fix(deps): update android.gradle.plugin to v8.9.1 by @renovate in https://github.com/element-hq/element-x-android/pull/4465
### Others
* Sync Strings - tweaks to identity change messages by @andybalaam in https://github.com/element-hq/element-x-android/pull/4454
* Check link click by @bmarty in https://github.com/element-hq/element-x-android/pull/4463
**Full Changelog**: https://github.com/element-hq/element-x-android/compare/v25.03.2...v25.03.3
Changes in Element X v25.03.2 Changes in Element X v25.03.2
============================= =============================

View file

@ -0,0 +1,2 @@
Main changes in this version: added a fix for Element Call not being able to report issues.
Full changelog: https://github.com/element-hq/element-x-android/releases

View file

@ -23,6 +23,7 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.media3.common.MimeTypes import androidx.media3.common.MimeTypes
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import dagger.assisted.Assisted import dagger.assisted.Assisted
@ -84,11 +85,13 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -135,7 +138,6 @@ class MessageComposerPresenter @AssistedInject constructor(
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var showTextFormatting: Boolean by mutableStateOf(false) internal var showTextFormatting: Boolean by mutableStateOf(false)
@OptIn(FlowPreview::class)
@SuppressLint("UnsafeOptInUsageError") @SuppressLint("UnsafeOptInUsageError")
@Composable @Composable
override fun present(): MessageComposerState { override fun present(): MessageComposerState {
@ -148,12 +150,6 @@ class MessageComposerPresenter @AssistedInject constructor(
richTextEditorState.isReadyToProcessActions = true richTextEditorState.isReadyToProcessActions = true
} }
val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false) val markdownTextEditorState = rememberMarkdownTextEditorState(initialText = null, initialFocus = false)
var isMentionsEnabled by remember { mutableStateOf(false) }
var isRoomAliasSuggestionsEnabled by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
isMentionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.Mentions)
isRoomAliasSuggestionsEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.RoomAliasSuggestions)
}
val cameraPermissionState = cameraPermissionPresenter.present() val cameraPermissionState = cameraPermissionPresenter.present()
@ -187,8 +183,6 @@ class MessageComposerPresenter @AssistedInject constructor(
val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true) val sendTypingNotifications by sessionPreferencesStore.isSendTypingNotificationsEnabled().collectAsState(initial = true)
val roomAliasSuggestions by roomAliasSuggestionsDataSource.getAllRoomAliasSuggestions().collectAsState(initial = emptyList())
LaunchedEffect(cameraPermissionState.permissionGranted) { LaunchedEffect(cameraPermissionState.permissionGranted) {
if (cameraPermissionState.permissionGranted) { if (cameraPermissionState.permissionGranted) {
when (pendingEvent) { when (pendingEvent) {
@ -201,35 +195,7 @@ class MessageComposerPresenter @AssistedInject constructor(
} }
val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() } val suggestions = remember { mutableStateListOf<ResolvedSuggestion>() }
LaunchedEffect(isMentionsEnabled) { ResolveSuggestionsEffect(suggestions)
if (!isMentionsEnabled) return@LaunchedEffect
val currentUserId = room.sessionId
suspend fun canSendRoomMention(): Boolean {
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
return !room.isDm() && userCanSendAtRoom
}
// This will trigger a search immediately when `@` is typed
val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() }
// This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
merge(mentionStartTrigger, mentionCompletionTrigger)
.combine(room.membersStateFlow) { suggestion, roomMembersState ->
suggestions.clear()
val result = suggestionsProcessor.process(
suggestion = suggestion,
roomMembersState = roomMembersState,
roomAliasSuggestions = if (isRoomAliasSuggestionsEnabled) roomAliasSuggestions else emptyList(),
currentUserId = currentUserId,
canSendRoomMention = ::canSendRoomMention,
)
if (result.isNotEmpty()) {
suggestions.addAll(result)
}
}
.collect()
}
DisposableEffect(Unit) { DisposableEffect(Unit) {
// Declare that the user is not typing anymore when the composer is disposed // Declare that the user is not typing anymore when the composer is disposed
@ -409,6 +375,45 @@ class MessageComposerPresenter @AssistedInject constructor(
) )
} }
@OptIn(FlowPreview::class)
@Composable
private fun ResolveSuggestionsEffect(
suggestions: SnapshotStateList<ResolvedSuggestion>,
) {
LaunchedEffect(Unit) {
val currentUserId = room.sessionId
suspend fun canSendRoomMention(): Boolean {
val userCanSendAtRoom = room.canUserTriggerRoomNotification(currentUserId).getOrDefault(false)
return !room.isDm() && userCanSendAtRoom
}
// This will trigger a search immediately when `@` is typed
val mentionStartTrigger = suggestionSearchTrigger.filter { it?.text.isNullOrEmpty() }
// This will start a search when the user changes the text after the `@` with a debounce to prevent too much wasted work
val mentionCompletionTrigger = suggestionSearchTrigger.debounce(0.3.seconds).filter { !it?.text.isNullOrEmpty() }
val mentionTriggerFlow = merge(mentionStartTrigger, mentionCompletionTrigger)
val roomAliasSuggestionsFlow = roomAliasSuggestionsDataSource
.getAllRoomAliasSuggestions()
.stateIn(this, SharingStarted.Lazily, emptyList())
combine(mentionTriggerFlow, room.membersStateFlow, roomAliasSuggestionsFlow) { suggestion, roomMembersState, roomAliasSuggestions ->
val result = suggestionsProcessor.process(
suggestion = suggestion,
roomMembersState = roomMembersState,
roomAliasSuggestions = roomAliasSuggestions,
currentUserId = currentUserId,
canSendRoomMention = ::canSendRoomMention,
)
suggestions.clear()
suggestions.addAll(result)
}
.collect()
}
}
private fun CoroutineScope.sendMessage( private fun CoroutineScope.sendMessage(
markdownTextEditorState: MarkdownTextEditorState, markdownTextEditorState: MarkdownTextEditorState,
richTextEditorState: RichTextEditorState, richTextEditorState: RichTextEditorState,

View file

@ -53,7 +53,11 @@ class SuggestionsProcessor @Inject constructor() {
} }
SuggestionType.Room -> { SuggestionType.Room -> {
roomAliasSuggestions roomAliasSuggestions
.filter { it.roomAlias.value.contains(suggestion.text, ignoreCase = true) } .filter { roomAliasSuggestion ->
// Filter by either room alias or room name (if available)
roomAliasSuggestion.roomAlias.value.contains(suggestion.text, ignoreCase = true) ||
roomAliasSuggestion.roomName?.contains(suggestion.text, ignoreCase = true) == true
}
.map { .map {
ResolvedSuggestion.Alias( ResolvedSuggestion.Alias(
roomAlias = it.roomAlias, roomAlias = it.roomAlias,

View file

@ -61,7 +61,6 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_3
import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.A_USER_ID_4
import io.element.android.libraries.matrix.test.core.aBuildMeta
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
@ -1011,16 +1010,10 @@ class MessageComposerPresenterTest {
) )
givenRoomInfo(aRoomInfo(isDirect = false)) givenRoomInfo(aRoomInfo(isDirect = false))
} }
val flagsService = FakeFeatureFlagService( val presenter = createPresenter(this, room)
mapOf(
FeatureFlags.Mentions.key to true,
)
)
val presenter = createPresenter(this, room, featureFlagService = flagsService)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
// A null suggestion (no suggestion was received) returns nothing // A null suggestion (no suggestion was received) returns nothing
@ -1078,16 +1071,10 @@ class MessageComposerPresenterTest {
) )
) )
} }
val flagsService = FakeFeatureFlagService( val presenter = createPresenter(this, room)
mapOf(
FeatureFlags.Mentions.key to true,
)
)
val presenter = createPresenter(this, room, featureFlagService = flagsService)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
// An empty suggestion returns the joined members that are not the current user, but not the room // An empty suggestion returns the joined members that are not the current user, but not the room
@ -1293,11 +1280,11 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
awaitFirstItem().also { state -> skipItems(2)
awaitItem().also { state ->
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
assertThat(state.textEditorState.messageHtml()).isNull() assertThat(state.textEditorState.messageHtml()).isNull()
} }
assert(loadDraftLambda) assert(loadDraftLambda)
.isCalledOnce() .isCalledOnce()
.with(value(A_ROOM_ID), value(false)) .with(value(A_ROOM_ID), value(false))
@ -1327,7 +1314,8 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
awaitFirstItem().also { state -> skipItems(1)
awaitItem().also { state ->
assertThat(state.showTextFormatting).isTrue() assertThat(state.showTextFormatting).isTrue()
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
assertThat(state.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
@ -1360,7 +1348,8 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
awaitFirstItem().also { state -> skipItems(2)
awaitItem().also { state ->
assertThat(state.showTextFormatting).isFalse() assertThat(state.showTextFormatting).isFalse()
assertThat(state.mode).isEqualTo(anEditMode()) assertThat(state.mode).isEqualTo(anEditMode())
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
@ -1406,7 +1395,8 @@ class MessageComposerPresenterTest {
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
awaitFirstItem().also { state -> skipItems(2)
awaitItem().also { state ->
assertThat(state.showTextFormatting).isFalse() assertThat(state.showTextFormatting).isFalse()
assertThat(state.mode).isEqualTo(aReplyMode()) assertThat(state.mode).isEqualTo(aReplyMode())
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
@ -1580,8 +1570,7 @@ class MessageComposerPresenterTest {
} }
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T { private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
// Skip 2 item if Mentions feature is enabled, else 1 skipItems(1)
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
return awaitItem() return awaitItem()
} }
} }

View file

@ -133,7 +133,7 @@ class SuggestionsProcessorTest {
} }
@Test @Test
fun `processing Room suggestion with aliases will return a suggestion`() = runTest { fun `processing Room suggestion with aliases will return a suggestion when matching on alias`() = runTest {
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS) val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
val result = suggestionsProcessor.process( val result = suggestionsProcessor.process(
suggestion = aRoomSuggestion("ali"), suggestion = aRoomSuggestion("ali"),
@ -171,7 +171,56 @@ class SuggestionsProcessorTest {
RoomAliasSuggestion( RoomAliasSuggestion(
roomAlias = A_ROOM_ALIAS, roomAlias = A_ROOM_ALIAS,
roomId = aRoomSummary.roomId, roomId = aRoomSummary.roomId,
roomName = aRoomSummary.info.name, roomName = "Element",
roomAvatarUrl = aRoomSummary.info.avatarUrl,
)
),
currentUserId = A_USER_ID,
canSendRoomMention = { true },
)
assertThat(result).isEmpty()
}
@Test
fun `processing Room suggestion will return a suggestion when matching on room name`() = runTest {
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
val result = suggestionsProcessor.process(
suggestion = aRoomSuggestion("lement"),
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
roomAliasSuggestions = listOf(
RoomAliasSuggestion(
roomAlias = A_ROOM_ALIAS,
roomId = aRoomSummary.roomId,
roomName = "Element",
roomAvatarUrl = aRoomSummary.info.avatarUrl,
)
),
currentUserId = A_USER_ID,
canSendRoomMention = { true },
)
assertThat(result).isEqualTo(
listOf(
ResolvedSuggestion.Alias(
roomAlias = A_ROOM_ALIAS,
roomId = aRoomSummary.roomId,
roomName = "Element",
roomAvatarUrl = aRoomSummary.info.avatarUrl,
)
)
)
}
@Test
fun `processing Room suggestion will not return a suggestion when room has no name`() = runTest {
val aRoomSummary = aRoomSummary(canonicalAlias = A_ROOM_ALIAS)
val result = suggestionsProcessor.process(
suggestion = aRoomSuggestion("lement"),
roomMembersState = MatrixRoomMembersState.Ready(persistentListOf()),
roomAliasSuggestions = listOf(
RoomAliasSuggestion(
roomAlias = A_ROOM_ALIAS,
roomId = aRoomSummary.roomId,
roomName = null,
roomAvatarUrl = aRoomSummary.info.avatarUrl, roomAvatarUrl = aRoomSummary.info.avatarUrl,
) )
), ),

View file

@ -80,7 +80,7 @@ class DeveloperSettingsPresenterTest {
presenter.test { presenter.test {
skipItems(2) skipItems(2)
awaitItem().also { state -> awaitItem().also { state ->
val feature = state.features.first() val feature = state.features.first { !it.isEnabled }
state.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(feature, !feature.isEnabled)) state.eventSink(DeveloperSettingsEvents.UpdateEnabledFeature(feature, !feature.isEnabled))
} }
awaitItem().also { state -> awaitItem().also { state ->

View file

@ -30,7 +30,6 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS
import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.matrix.api.verification.VerificationRequest
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -57,7 +56,7 @@ class IncomingVerificationPresenter @AssistedInject constructor(
@Composable @Composable
override fun present(): IncomingVerificationState { override fun present(): IncomingVerificationState {
val coroutineScope = rememberCoroutineScope { Dispatchers.IO } val coroutineScope = rememberCoroutineScope()
val stateAndDispatch = stateMachine.rememberStateAndDispatch() val stateAndDispatch = stateMachine.rememberStateAndDispatch()

View file

@ -131,6 +131,9 @@ class IncomingVerificationPresenterTest {
isWaiting = false, isWaiting = false,
) )
) )
advanceTimeBy(1.seconds)
resetLambda.assertions().isCalledOnce().with(value(false)) resetLambda.assertions().isCalledOnce().with(value(false))
acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest)) acknowledgeVerificationRequestLambda.assertions().isCalledOnce().with(value(anIncomingSessionVerificationRequest))
acceptVerificationRequestLambda.assertions().isNeverCalled() acceptVerificationRequestLambda.assertions().isNeverCalled()
@ -139,7 +142,9 @@ class IncomingVerificationPresenterTest {
skipItems(1) skipItems(1)
val initialWaitingState = awaitItem() val initialWaitingState = awaitItem()
assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue() assertThat((initialWaitingState.step as IncomingVerificationState.Step.Initial).isWaiting).isTrue()
advanceUntilIdle()
advanceTimeBy(1.seconds)
acceptVerificationRequestLambda.assertions().isCalledOnce() acceptVerificationRequestLambda.assertions().isCalledOnce()
// Remote sent the data // Remote sent the data
fakeSessionVerificationService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest) fakeSessionVerificationService.emitVerificationFlowState(VerificationFlowState.DidAcceptVerificationRequest)

View file

@ -50,10 +50,10 @@ wysiwyg = "2.38.3"
telephoto = "0.15.1" telephoto = "0.15.1"
# Dependency analysis # Dependency analysis
dependencyAnalysis = "2.13.1" dependencyAnalysis = "2.13.2"
# DI # DI
dagger = "2.56" dagger = "2.56.1"
anvil = "0.4.1" anvil = "0.4.1"
# Auto service # Auto service
@ -212,7 +212,7 @@ anvil_compiler_api = { module = "dev.zacsweers.anvil:compiler-api", version.ref
anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" } anvil_compiler_utils = { module = "dev.zacsweers.anvil:compiler-utils", version.ref = "anvil" }
# Element Call # Element Call
element_call_embedded = "io.element.android:element-call-embedded:0.9.0-rc.2" element_call_embedded = "io.element.android:element-call-embedded:0.9.0-rc.4"
# Auto services # Auto services
google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" } google_autoservice = { module = "com.google.auto.service:auto-service", version.ref = "autoservice" }

View file

@ -54,20 +54,6 @@ enum class FeatureFlags(
defaultValue = { true }, defaultValue = { true },
isFinished = true, isFinished = true,
), ),
Mentions(
key = "feature.mentions",
title = "Mentions",
description = "Type `@` to get mention suggestions and insert them",
defaultValue = { true },
isFinished = false,
),
RoomAliasSuggestions(
key = "feature.roomAliasSuggestions",
title = "Room alias suggestions",
description = "Type `#` to get room alias suggestions and insert them",
defaultValue = { false },
isFinished = false,
),
MarkAsUnread( MarkAsUnread(
key = "feature.markAsUnread", key = "feature.markAsUnread",
title = "Mark as unread", title = "Mark as unread",

View file

@ -32,7 +32,7 @@ private const val versionYear = 25
private const val versionMonth = 3 private const val versionMonth = 3
// Note: must be in [0,99] // Note: must be in [0,99]
private const val versionReleaseNumber = 3 private const val versionReleaseNumber = 4
object Versions { object Versions {
const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber const val VERSION_CODE = (2000 + versionYear) * 10_000 + versionMonth * 100 + versionReleaseNumber