Merge pull request #5160 from element-hq/feature/bma/cleanupFeatureFlags

Remove old feature flags
This commit is contained in:
Benoit Marty 2025-08-12 18:21:44 +02:00 committed by GitHub
commit 0a4a4e20db
77 changed files with 310 additions and 1243 deletions

View file

@ -147,14 +147,6 @@
<data android:host="user" /> <data android:host="user" />
<data android:host="room" /> <data android:host="room" />
</intent-filter> </intent-filter>
</activity>
<!-- Using an activity-alias for incoming share intent, in order
to be able to disable the feature programmatically -->
<activity-alias
android:name=".ShareActivity"
android:exported="true"
android:targetActivity=".MainActivity">
<!-- Incoming share simple --> <!-- Incoming share simple -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
@ -171,7 +163,7 @@
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.OPENABLE" />
</intent-filter> </intent-filter>
</activity-alias> </activity>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"

View file

@ -8,9 +8,6 @@
package io.element.android.appconfig package io.element.android.appconfig
object OnBoardingConfig { object OnBoardingConfig {
/** Whether the user can use QR code login. */
const val CAN_LOGIN_WITH_QR_CODE = true
/** Whether the user can create an account using the app. */ /** Whether the user can create an account using the app. */
const val CAN_CREATE_ACCOUNT = true const val CAN_CREATE_ACCOUNT = true
} }

View file

@ -63,7 +63,6 @@ dependencies {
testImplementation(projects.features.networkmonitor.test) testImplementation(projects.features.networkmonitor.test)
testImplementation(projects.tests.testutils) testImplementation(projects.tests.testutils)
testImplementation(projects.features.rageshake.test) testImplementation(projects.features.rageshake.test)
testImplementation(projects.features.share.test)
testImplementation(projects.services.appnavstate.test) testImplementation(projects.services.appnavstate.test)
testImplementation(projects.services.analytics.test) testImplementation(projects.services.analytics.test)
testImplementation(libs.test.appyx.junit) testImplementation(libs.test.appyx.junit)

View file

@ -14,7 +14,6 @@ import androidx.compose.runtime.getValue
import im.vector.app.features.analytics.plan.SuperProperties import im.vector.app.features.analytics.plan.SuperProperties
import io.element.android.features.rageshake.api.crash.CrashDetectionState import io.element.android.features.rageshake.api.crash.CrashDetectionState
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
import io.element.android.features.share.api.ShareService
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.matrix.api.SdkMetadata import io.element.android.libraries.matrix.api.SdkMetadata
import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analytics.api.AnalyticsService
@ -26,7 +25,6 @@ class RootPresenter @Inject constructor(
private val rageshakeDetectionPresenter: Presenter<RageshakeDetectionState>, private val rageshakeDetectionPresenter: Presenter<RageshakeDetectionState>,
private val appErrorStateService: AppErrorStateService, private val appErrorStateService: AppErrorStateService,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val shareService: ShareService,
private val sdkMetadata: SdkMetadata, private val sdkMetadata: SdkMetadata,
) : Presenter<RootState> { ) : Presenter<RootState> {
@Composable @Composable
@ -45,10 +43,6 @@ class RootPresenter @Inject constructor(
) )
} }
LaunchedEffect(Unit) {
shareService.observeFeatureFlag(this)
}
return RootState( return RootState(
rageshakeDetectionState = rageshakeDetectionState, rageshakeDetectionState = rageshakeDetectionState,
crashDetectionState = crashDetectionState, crashDetectionState = crashDetectionState,

View file

@ -14,16 +14,12 @@ import com.google.common.truth.Truth.assertThat
import io.element.android.appnav.root.RootPresenter import io.element.android.appnav.root.RootPresenter
import io.element.android.features.rageshake.api.crash.aCrashDetectionState import io.element.android.features.rageshake.api.crash.aCrashDetectionState
import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState
import io.element.android.features.share.api.ShareService
import io.element.android.features.share.test.FakeShareService
import io.element.android.libraries.matrix.test.FakeSdkMetadata import io.element.android.libraries.matrix.test.FakeSdkMetadata
import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.services.apperror.api.AppErrorState import io.element.android.services.apperror.api.AppErrorState
import io.element.android.services.apperror.api.AppErrorStateService import io.element.android.services.apperror.api.AppErrorStateService
import io.element.android.services.apperror.impl.DefaultAppErrorStateService import io.element.android.services.apperror.impl.DefaultAppErrorStateService
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -43,22 +39,6 @@ class RootPresenterTest {
} }
} }
@Test
fun `present - check that share service is invoked`() = runTest {
val lambda = lambdaRecorder<CoroutineScope, Unit> { _ -> }
val presenter = createRootPresenter(
shareService = FakeShareService {
lambda(it)
}
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
skipItems(1)
lambda.assertions().isCalledOnce()
}
}
@Test @Test
fun `present - passes app error state`() = runTest { fun `present - passes app error state`() = runTest {
val presenter = createRootPresenter( val presenter = createRootPresenter(
@ -82,14 +62,12 @@ class RootPresenterTest {
private fun createRootPresenter( private fun createRootPresenter(
appErrorService: AppErrorStateService = DefaultAppErrorStateService(), appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
shareService: ShareService = FakeShareService {},
): RootPresenter { ): RootPresenter {
return RootPresenter( return RootPresenter(
crashDetectionPresenter = { aCrashDetectionState() }, crashDetectionPresenter = { aCrashDetectionState() },
rageshakeDetectionPresenter = { aRageshakeDetectionState() }, rageshakeDetectionPresenter = { aRageshakeDetectionState() },
appErrorStateService = appErrorService, appErrorStateService = appErrorService,
analyticsService = FakeAnalyticsService(), analyticsService = FakeAnalyticsService(),
shareService = shareService,
sdkMetadata = FakeSdkMetadata("sha") sdkMetadata = FakeSdkMetadata("sha")
) )
} }

View file

@ -101,36 +101,34 @@ private fun RoomListModalBottomSheetContent(
) )
} }
) )
if (contextMenu.markAsUnreadFeatureFlagEnabled) { if (contextMenu.hasNewContent) {
if (contextMenu.hasNewContent) { ListItem(
ListItem( headlineContent = {
headlineContent = { Text(
Text( text = stringResource(id = R.string.screen_roomlist_mark_as_read),
text = stringResource(id = R.string.screen_roomlist_mark_as_read), style = MaterialTheme.typography.bodyLarge,
style = MaterialTheme.typography.bodyLarge, )
) },
}, onClick = onRoomMarkReadClick,
onClick = onRoomMarkReadClick, leadingContent = ListItemContent.Icon(
leadingContent = ListItemContent.Icon( iconSource = IconSource.Vector(CompoundIcons.MarkAsRead())
iconSource = IconSource.Vector(CompoundIcons.MarkAsRead()) ),
), style = ListItemStyle.Primary,
style = ListItemStyle.Primary, )
) } else {
} else { ListItem(
ListItem( headlineContent = {
headlineContent = { Text(
Text( text = stringResource(id = R.string.screen_roomlist_mark_as_unread),
text = stringResource(id = R.string.screen_roomlist_mark_as_unread), style = MaterialTheme.typography.bodyLarge,
style = MaterialTheme.typography.bodyLarge, )
) },
}, onClick = onRoomMarkUnreadClick,
onClick = onRoomMarkUnreadClick, leadingContent = ListItemContent.Icon(
leadingContent = ListItemContent.Icon( iconSource = IconSource.Vector(CompoundIcons.MarkAsUnread())
iconSource = IconSource.Vector(CompoundIcons.MarkAsUnread()) ),
), style = ListItemStyle.Primary,
style = ListItemStyle.Primary, )
)
}
} }
ListItem( ListItem(
headlineContent = { headlineContent = {

View file

@ -36,8 +36,6 @@ import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -74,7 +72,6 @@ class RoomListPresenter @Inject constructor(
private val client: MatrixClient, private val client: MatrixClient,
private val leaveRoomPresenter: Presenter<LeaveRoomState>, private val leaveRoomPresenter: Presenter<LeaveRoomState>,
private val roomListDataSource: RoomListDataSource, private val roomListDataSource: RoomListDataSource,
private val featureFlagService: FeatureFlagService,
private val filtersPresenter: Presenter<RoomListFiltersState>, private val filtersPresenter: Presenter<RoomListFiltersState>,
private val searchPresenter: Presenter<RoomListSearchState>, private val searchPresenter: Presenter<RoomListSearchState>,
private val sessionPreferencesStore: SessionPreferencesStore, private val sessionPreferencesStore: SessionPreferencesStore,
@ -244,7 +241,6 @@ class RoomListPresenter @Inject constructor(
roomName = event.roomSummary.name, roomName = event.roomSummary.name,
isDm = event.roomSummary.isDm, isDm = event.roomSummary.isDm,
isFavorite = event.roomSummary.isFavorite, isFavorite = event.roomSummary.isFavorite,
markAsUnreadFeatureFlagEnabled = featureFlagService.isFeatureEnabled(FeatureFlags.MarkAsUnread),
hasNewContent = event.roomSummary.hasNewContent, hasNewContent = event.roomSummary.hasNewContent,
displayClearRoomCacheAction = appPreferencesStore.isDeveloperModeEnabledFlow().first(), displayClearRoomCacheAction = appPreferencesStore.isDeveloperModeEnabledFlow().first(),
) )

View file

@ -41,7 +41,6 @@ data class RoomListState(
val roomName: String?, val roomName: String?,
val isDm: Boolean, val isDm: Boolean,
val isFavorite: Boolean, val isFavorite: Boolean,
val markAsUnreadFeatureFlagEnabled: Boolean,
val hasNewContent: Boolean, val hasNewContent: Boolean,
val displayClearRoomCacheAction: Boolean, val displayClearRoomCacheAction: Boolean,
) : ContextMenu ) : ContextMenu

View file

@ -28,7 +28,6 @@ internal fun aContextMenuShown(
roomId = RoomId("!aRoom:aDomain"), roomId = RoomId("!aRoom:aDomain"),
roomName = roomName, roomName = roomName,
isDm = isDm, isDm = isDm,
markAsUnreadFeatureFlagEnabled = true,
hasNewContent = hasNewContent, hasNewContent = hasNewContent,
isFavorite = isFavorite, isFavorite = isFavorite,
displayClearRoomCacheAction = false, displayClearRoomCacheAction = false,

View file

@ -32,8 +32,6 @@ import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -217,7 +215,6 @@ class RoomListPresenterTest {
roomName = summary.name, roomName = summary.name,
isDm = false, isDm = false,
isFavorite = false, isFavorite = false,
markAsUnreadFeatureFlagEnabled = true,
hasNewContent = false, hasNewContent = false,
displayClearRoomCacheAction = false, displayClearRoomCacheAction = false,
) )
@ -235,7 +232,6 @@ class RoomListPresenterTest {
roomName = summary.name, roomName = summary.name,
isDm = false, isDm = false,
isFavorite = true, isFavorite = true,
markAsUnreadFeatureFlagEnabled = true,
hasNewContent = false, hasNewContent = false,
displayClearRoomCacheAction = false, displayClearRoomCacheAction = false,
) )
@ -263,7 +259,6 @@ class RoomListPresenterTest {
roomName = summary.name, roomName = summary.name,
isDm = false, isDm = false,
isFavorite = false, isFavorite = false,
markAsUnreadFeatureFlagEnabled = true,
// true here. // true here.
hasNewContent = false, hasNewContent = false,
displayClearRoomCacheAction = true, displayClearRoomCacheAction = true,
@ -295,7 +290,6 @@ class RoomListPresenterTest {
roomName = summary.name, roomName = summary.name,
isDm = false, isDm = false,
isFavorite = false, isFavorite = false,
markAsUnreadFeatureFlagEnabled = true,
hasNewContent = false, hasNewContent = false,
displayClearRoomCacheAction = false, displayClearRoomCacheAction = false,
) )
@ -604,7 +598,6 @@ class RoomListPresenterTest {
dateFormatter: DateFormatter = FakeDateFormatter(), dateFormatter: DateFormatter = FakeDateFormatter(),
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(), roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
analyticsService: AnalyticsService = FakeAnalyticsService(), analyticsService: AnalyticsService = FakeAnalyticsService(),
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() }, filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() }, searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
@ -626,7 +619,6 @@ class RoomListPresenterTest {
sessionCoroutineScope = backgroundScope, sessionCoroutineScope = backgroundScope,
dateTimeObserver = FakeDateTimeObserver(), dateTimeObserver = FakeDateTimeObserver(),
), ),
featureFlagService = featureFlagService,
searchPresenter = searchPresenter, searchPresenter = searchPresenter,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
filtersPresenter = filtersPresenter, filtersPresenter = filtersPresenter,

View file

@ -67,6 +67,7 @@ internal class AndroidLocationActionsTest {
accuracy = 0f accuracy = 0f
) )
// Set a locale with comma as decimal separator // Set a locale with comma as decimal separator
@Suppress("DEPRECATION")
Locale.setDefault(Locale.Category.FORMAT, Locale("pt", "BR")) Locale.setDefault(Locale.Category.FORMAT, Locale("pt", "BR"))
val actual = buildUrl(location, "(weird/stuff here)", ::urlEncoder) val actual = buildUrl(location, "(weird/stuff here)", ::urlEncoder)

View file

@ -18,8 +18,6 @@ import io.element.android.features.lockscreen.impl.storage.LockScreenStore
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.di.annotations.AppCoroutineScope import io.element.android.libraries.di.annotations.AppCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.sessionstorage.api.observer.SessionListener import io.element.android.libraries.sessionstorage.api.observer.SessionListener
import io.element.android.libraries.sessionstorage.api.observer.SessionObserver import io.element.android.libraries.sessionstorage.api.observer.SessionObserver
import io.element.android.services.appnavstate.api.AppForegroundStateService import io.element.android.services.appnavstate.api.AppForegroundStateService
@ -29,7 +27,6 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -40,7 +37,6 @@ import kotlin.time.Duration
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultLockScreenService @Inject constructor( class DefaultLockScreenService @Inject constructor(
private val lockScreenConfig: LockScreenConfig, private val lockScreenConfig: LockScreenConfig,
private val featureFlagService: FeatureFlagService,
private val lockScreenStore: LockScreenStore, private val lockScreenStore: LockScreenStore,
private val pinCodeManager: PinCodeManager, private val pinCodeManager: PinCodeManager,
@AppCoroutineScope @AppCoroutineScope
@ -108,12 +104,7 @@ class DefaultLockScreenService @Inject constructor(
} }
override fun isPinSetup(): Flow<Boolean> { override fun isPinSetup(): Flow<Boolean> {
return combine( return pinCodeManager.hasPinCode()
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinUnlock),
pinCodeManager.hasPinCode()
) { isEnabled, hasPinCode ->
isEnabled && hasPinCode
}
} }
override fun isSetupRequired(): Flow<Boolean> { override fun isSetupRequired(): Flow<Boolean> {

View file

@ -27,14 +27,11 @@ import io.element.android.features.login.impl.login.LoginHelper
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
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.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.ui.utils.MultipleTapToUnlock import io.element.android.libraries.ui.utils.MultipleTapToUnlock
class OnBoardingPresenter @AssistedInject constructor( class OnBoardingPresenter @AssistedInject constructor(
@Assisted private val params: OnBoardingNode.Params, @Assisted private val params: OnBoardingNode.Params,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val featureFlagService: FeatureFlagService,
private val enterpriseService: EnterpriseService, private val enterpriseService: EnterpriseService,
private val defaultAccountProviderAccessControl: DefaultAccountProviderAccessControl, private val defaultAccountProviderAccessControl: DefaultAccountProviderAccessControl,
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability, private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
@ -80,8 +77,7 @@ class OnBoardingPresenter @AssistedInject constructor(
forcedAccountProvider ?: linkAccountProvider forcedAccountProvider ?: linkAccountProvider
} }
val canLoginWithQrCode by produceState(initialValue = false, linkAccountProvider) { val canLoginWithQrCode by produceState(initialValue = false, linkAccountProvider) {
value = linkAccountProvider == null && value = linkAccountProvider == null
featureFlagService.isFeatureEnabled(FeatureFlags.QrCodeLogin)
} }
val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false) val canReportBug by remember { rageshakeFeatureAvailability.isAvailable() }.collectAsState(false)
var showReportBug by rememberSaveable { mutableStateOf(false) } var showReportBug by rememberSaveable { mutableStateOf(false) }

View file

@ -19,9 +19,6 @@ import io.element.android.features.login.impl.web.WebClientUrlForAuthenticationR
import io.element.android.features.wellknown.test.FakeWellknownRetriever import io.element.android.features.wellknown.test.FakeWellknownRetriever
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER
import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2 import io.element.android.libraries.matrix.test.AN_ACCOUNT_PROVIDER_2
@ -70,13 +67,8 @@ class OnBoardingPresenterTest {
productionApplicationName = "B", productionApplicationName = "B",
desktopApplicationName = "C", desktopApplicationName = "C",
) )
val featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
buildMeta = buildMeta,
)
val presenter = createPresenter( val presenter = createPresenter(
buildMeta = buildMeta, buildMeta = buildMeta,
featureFlagService = featureFlagService,
enterpriseService = FakeEnterpriseService( enterpriseService = FakeEnterpriseService(
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
), ),
@ -131,9 +123,6 @@ class OnBoardingPresenterTest {
accountProvider = ACCOUNT_PROVIDER_FROM_LINK, accountProvider = ACCOUNT_PROVIDER_FROM_LINK,
loginHint = null, loginHint = null,
), ),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
),
enterpriseService = FakeEnterpriseService( enterpriseService = FakeEnterpriseService(
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) }, defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, EnterpriseService.ANY_ACCOUNT_PROVIDER) },
isAllowedToConnectToHomeserverResult = { true }, isAllowedToConnectToHomeserverResult = { true },
@ -156,9 +145,6 @@ class OnBoardingPresenterTest {
accountProvider = ACCOUNT_PROVIDER_FROM_LINK, accountProvider = ACCOUNT_PROVIDER_FROM_LINK,
loginHint = null, loginHint = null,
), ),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
),
enterpriseService = FakeEnterpriseService( enterpriseService = FakeEnterpriseService(
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, ACCOUNT_PROVIDER_FROM_CONFIG_2) }, defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG, ACCOUNT_PROVIDER_FROM_CONFIG_2) },
isAllowedToConnectToHomeserverResult = { false }, isAllowedToConnectToHomeserverResult = { false },
@ -181,9 +167,6 @@ class OnBoardingPresenterTest {
accountProvider = ACCOUNT_PROVIDER_FROM_LINK, accountProvider = ACCOUNT_PROVIDER_FROM_LINK,
loginHint = null, loginHint = null,
), ),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.QrCodeLogin.key to true),
),
enterpriseService = FakeEnterpriseService( enterpriseService = FakeEnterpriseService(
defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG) }, defaultHomeserverListResult = { listOf(ACCOUNT_PROVIDER_FROM_CONFIG) },
) )
@ -237,7 +220,6 @@ class OnBoardingPresenterTest {
private fun createPresenter( private fun createPresenter(
params: OnBoardingNode.Params = OnBoardingNode.Params(null, null), params: OnBoardingNode.Params = OnBoardingNode.Params(null, null),
buildMeta: BuildMeta = aBuildMeta(), buildMeta: BuildMeta = aBuildMeta(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
enterpriseService: EnterpriseService = FakeEnterpriseService(), enterpriseService: EnterpriseService = FakeEnterpriseService(),
wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(), wellknownRetriever: WellknownRetriever = FakeWellknownRetriever(),
rageshakeFeatureAvailability: () -> Flow<Boolean> = { flowOf(true) }, rageshakeFeatureAvailability: () -> Flow<Boolean> = { flowOf(true) },
@ -245,7 +227,6 @@ private fun createPresenter(
) = OnBoardingPresenter( ) = OnBoardingPresenter(
params = params, params = params,
buildMeta = buildMeta, buildMeta = buildMeta,
featureFlagService = featureFlagService,
enterpriseService = enterpriseService, enterpriseService = enterpriseService,
defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl( defaultAccountProviderAccessControl = DefaultAccountProviderAccessControl(
enterpriseService = enterpriseService, enterpriseService = enterpriseService,

View file

@ -1,15 +0,0 @@
/*
* Copyright 2024 New Vector 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.api.pinned
import androidx.compose.runtime.Composable
fun interface IsPinnedMessagesFeatureEnabled {
@Composable
operator fun invoke(): Boolean
}

View file

@ -63,8 +63,6 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.encryption.EncryptionService import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@ -111,7 +109,6 @@ class MessagesPresenter @AssistedInject constructor(
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
private val featureFlagsService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider, private val htmlConverterProvider: HtmlConverterProvider,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val timelineController: TimelineController, private val timelineController: TimelineController,
@ -187,11 +184,6 @@ class MessagesPresenter @AssistedInject constructor(
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
var enableVoiceMessages by remember { mutableStateOf(false) }
LaunchedEffect(featureFlagsService) {
enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages)
}
var dmUserVerificationState by remember { mutableStateOf<IdentityState?>(null) } var dmUserVerificationState by remember { mutableStateOf<IdentityState?>(null) }
val membersState by room.membersStateFlow.collectAsState() val membersState by room.membersStateFlow.collectAsState()
@ -261,7 +253,6 @@ class MessagesPresenter @AssistedInject constructor(
showReinvitePrompt = showReinvitePrompt, showReinvitePrompt = showReinvitePrompt,
inviteProgress = inviteProgress.value, inviteProgress = inviteProgress.value,
enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING, enableTextFormatting = MessageComposerConfig.ENABLE_RICH_TEXT_EDITING,
enableVoiceMessages = enableVoiceMessages,
appName = buildMeta.applicationName, appName = buildMeta.applicationName,
roomCallState = roomCallState, roomCallState = roomCallState,
pinnedMessagesBannerState = pinnedMessagesBannerState, pinnedMessagesBannerState = pinnedMessagesBannerState,
@ -449,7 +440,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption( val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId, eventOrTransactionId = targetEvent.eventOrTransactionId,
content = "", content = "",
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
) )
composerState.eventSink( composerState.eventSink(
MessageComposerEvents.SetMode(composerMode) MessageComposerEvents.SetMode(composerMode)
@ -463,7 +453,6 @@ class MessagesPresenter @AssistedInject constructor(
val composerMode = MessageComposerMode.EditCaption( val composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = targetEvent.eventOrTransactionId, eventOrTransactionId = targetEvent.eventOrTransactionId,
content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(), content = (targetEvent.content as? TimelineItemEventContentWithAttachment)?.caption.orEmpty(),
showCaptionCompatibilityWarning = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaCaptionWarning),
) )
composerState.eventSink( composerState.eventSink(
MessageComposerEvents.SetMode(composerMode) MessageComposerEvents.SetMode(composerMode)

View file

@ -51,7 +51,6 @@ data class MessagesState(
val inviteProgress: AsyncData<Unit>, val inviteProgress: AsyncData<Unit>,
val showReinvitePrompt: Boolean, val showReinvitePrompt: Boolean,
val enableTextFormatting: Boolean, val enableTextFormatting: Boolean,
val enableVoiceMessages: Boolean,
val roomCallState: RoomCallState, val roomCallState: RoomCallState,
val appName: String, val appName: String,
val pinnedMessagesBannerState: PinnedMessagesBannerState, val pinnedMessagesBannerState: PinnedMessagesBannerState,

View file

@ -61,14 +61,12 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
aMessagesState(roomName = null), aMessagesState(roomName = null),
aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)), aMessagesState(composerState = aMessageComposerState(showTextFormatting = true)),
aMessagesState( aMessagesState(
enableVoiceMessages = true,
voiceMessageComposerState = aVoiceMessageComposerState(showPermissionRationaleDialog = true), voiceMessageComposerState = aVoiceMessageComposerState(showPermissionRationaleDialog = true),
), ),
aMessagesState( aMessagesState(
roomCallState = anOngoingCallState(), roomCallState = anOngoingCallState(),
), ),
aMessagesState( aMessagesState(
enableVoiceMessages = true,
voiceMessageComposerState = aVoiceMessageComposerState( voiceMessageComposerState = aVoiceMessageComposerState(
voiceMessageState = aVoiceMessagePreviewState(), voiceMessageState = aVoiceMessagePreviewState(),
showSendFailureDialog = true showSendFailureDialog = true
@ -113,7 +111,6 @@ fun aMessagesState(
reactionSummaryState: ReactionSummaryState = aReactionSummaryState(), reactionSummaryState: ReactionSummaryState = aReactionSummaryState(),
hasNetworkConnection: Boolean = true, hasNetworkConnection: Boolean = true,
showReinvitePrompt: Boolean = false, showReinvitePrompt: Boolean = false,
enableVoiceMessages: Boolean = true,
roomCallState: RoomCallState = aStandByCallState(), roomCallState: RoomCallState = aStandByCallState(),
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(), pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
dmUserVerificationState: IdentityState? = null, dmUserVerificationState: IdentityState? = null,
@ -141,7 +138,6 @@ fun aMessagesState(
inviteProgress = AsyncData.Uninitialized, inviteProgress = AsyncData.Uninitialized,
showReinvitePrompt = showReinvitePrompt, showReinvitePrompt = showReinvitePrompt,
enableTextFormatting = true, enableTextFormatting = true,
enableVoiceMessages = enableVoiceMessages,
roomCallState = roomCallState, roomCallState = roomCallState,
appName = "Element", appName = "Element",
pinnedMessagesBannerState = pinnedMessagesBannerState, pinnedMessagesBannerState = pinnedMessagesBannerState,

View file

@ -377,7 +377,7 @@ private fun MessagesViewContent(
enableTextFormatting = state.enableTextFormatting, enableTextFormatting = state.enableTextFormatting,
) )
if (state.enableVoiceMessages && state.voiceMessageComposerState.showPermissionRationaleDialog) { if (state.voiceMessageComposerState.showPermissionRationaleDialog) {
VoiceMessagePermissionRationaleDialog( VoiceMessagePermissionRationaleDialog(
onContinue = { onContinue = {
state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.AcceptPermissionRationale) state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.AcceptPermissionRationale)
@ -388,7 +388,7 @@ private fun MessagesViewContent(
appName = state.appName appName = state.appName
) )
} }
if (state.enableVoiceMessages && state.voiceMessageComposerState.showSendFailureDialog) { if (state.voiceMessageComposerState.showSendFailureDialog) {
VoiceMessageSendingFailedDialog( VoiceMessageSendingFailedDialog(
onDismiss = { state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) }, onDismiss = { state.voiceMessageComposerState.eventSink(VoiceMessageComposerEvents.DismissSendFailureDialog) },
) )
@ -464,7 +464,6 @@ private fun MessagesViewComposerBottomSheetContents(
MessageComposerView( MessageComposerView(
state = state.composerState, state = state.composerState,
voiceMessageState = state.voiceMessageComposerState, voiceMessageState = state.voiceMessageComposerState,
enableVoiceMessages = state.enableVoiceMessages,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) )
} }

View file

@ -18,7 +18,6 @@ import com.squareup.anvil.annotations.ContributesBinding
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.UserEventPermissions
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.actionlist.model.TimelineItemActionComparator import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionComparator
@ -40,8 +39,6 @@ import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.dateformatter.api.DateFormatter import io.element.android.libraries.dateformatter.api.DateFormatter
import io.element.android.libraries.dateformatter.api.DateFormatterMode import io.element.android.libraries.dateformatter.api.DateFormatterMode
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
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.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.AppPreferencesStore
@ -62,10 +59,8 @@ class DefaultActionListPresenter @AssistedInject constructor(
@Assisted @Assisted
private val postProcessor: TimelineItemActionPostProcessor, private val postProcessor: TimelineItemActionPostProcessor,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled,
private val room: BaseRoom, private val room: BaseRoom,
private val userSendFailureFactory: VerifiedUserSendFailureFactory, private val userSendFailureFactory: VerifiedUserSendFailureFactory,
private val featureFlagService: FeatureFlagService,
private val dateFormatter: DateFormatter, private val dateFormatter: DateFormatter,
) : ActionListPresenter { ) : ActionListPresenter {
@AssistedFactory @AssistedFactory
@ -87,7 +82,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
val isDeveloperModeEnabled by remember { val isDeveloperModeEnabled by remember {
appPreferencesStore.isDeveloperModeEnabledFlow() appPreferencesStore.isDeveloperModeEnabledFlow()
}.collectAsState(initial = false) }.collectAsState(initial = false)
val isPinnedEventsEnabled = isPinnedMessagesFeatureEnabled()
val pinnedEventIds by remember { val pinnedEventIds by remember {
room.roomInfoFlow.map { it.pinnedEventIds } room.roomInfoFlow.map { it.pinnedEventIds }
}.collectAsState(initial = persistentListOf()) }.collectAsState(initial = persistentListOf())
@ -99,7 +93,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
timelineItem = event.event, timelineItem = event.event,
usersEventPermissions = event.userEventPermissions, usersEventPermissions = event.userEventPermissions,
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isPinnedEventsEnabled = isPinnedEventsEnabled,
pinnedEventIds = pinnedEventIds, pinnedEventIds = pinnedEventIds,
target = target, target = target,
) )
@ -116,7 +109,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
timelineItem: TimelineItem.Event, timelineItem: TimelineItem.Event,
usersEventPermissions: UserEventPermissions, usersEventPermissions: UserEventPermissions,
isDeveloperModeEnabled: Boolean, isDeveloperModeEnabled: Boolean,
isPinnedEventsEnabled: Boolean,
pinnedEventIds: ImmutableList<EventId>, pinnedEventIds: ImmutableList<EventId>,
target: MutableState<ActionListState.Target> target: MutableState<ActionListState.Target>
) = launch { ) = launch {
@ -126,7 +118,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
timelineItem = timelineItem, timelineItem = timelineItem,
usersEventPermissions = usersEventPermissions, usersEventPermissions = usersEventPermissions,
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,
isPinnedEventsEnabled = isPinnedEventsEnabled,
isEventPinned = pinnedEventIds.contains(timelineItem.eventId), isEventPinned = pinnedEventIds.contains(timelineItem.eventId),
) )
@ -154,7 +145,6 @@ class DefaultActionListPresenter @AssistedInject constructor(
timelineItem: TimelineItem.Event, timelineItem: TimelineItem.Event,
usersEventPermissions: UserEventPermissions, usersEventPermissions: UserEventPermissions,
isDeveloperModeEnabled: Boolean, isDeveloperModeEnabled: Boolean,
isPinnedEventsEnabled: Boolean,
isEventPinned: Boolean, isEventPinned: Boolean,
): List<TimelineItemAction> { ): List<TimelineItemAction> {
val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther val canRedact = timelineItem.isMine && usersEventPermissions.canRedactOwn || !timelineItem.isMine && usersEventPermissions.canRedactOther
@ -173,9 +163,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
if (timelineItem.content is TimelineItemEventContentWithAttachment) { if (timelineItem.content is TimelineItemEventContentWithAttachment) {
// Caption // Caption
if (timelineItem.content.caption == null) { if (timelineItem.content.caption == null) {
if (featureFlagService.isFeatureEnabled(FeatureFlags.MediaCaptionCreation)) { add(TimelineItemAction.AddCaption)
add(TimelineItemAction.AddCaption)
}
} else { } else {
add(TimelineItemAction.EditCaption) add(TimelineItemAction.EditCaption)
add(TimelineItemAction.RemoveCaption) add(TimelineItemAction.RemoveCaption)
@ -189,7 +177,7 @@ class DefaultActionListPresenter @AssistedInject constructor(
if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) { if (canRedact && timelineItem.content is TimelineItemPollContent && !timelineItem.content.isEnded) {
add(TimelineItemAction.EndPoll) add(TimelineItemAction.EndPoll)
} }
val canPinUnpin = isPinnedEventsEnabled && usersEventPermissions.canPinUnpin && timelineItem.isRemote val canPinUnpin = usersEventPermissions.canPinUnpin && timelineItem.isRemote
if (canPinUnpin) { if (canPinUnpin) {
if (isEventPinned) { if (isEventPinned) {
add(TimelineItemAction.Unpin) add(TimelineItemAction.Unpin)

View file

@ -10,7 +10,6 @@ package io.element.android.features.messages.impl.attachments.preview
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -32,8 +31,6 @@ import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage
import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
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.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
@ -58,7 +55,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
private val mediaSender: MediaSender, private val mediaSender: MediaSender,
private val permalinkBuilder: PermalinkBuilder, private val permalinkBuilder: PermalinkBuilder,
private val temporaryUriDeleter: TemporaryUriDeleter, private val temporaryUriDeleter: TemporaryUriDeleter,
private val featureFlagService: FeatureFlagService,
private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory, private val mediaOptimizationSelectorPresenterFactory: MediaOptimizationSelectorPresenter.Factory,
@SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope, @SessionCoroutineScope private val sessionCoroutineScope: CoroutineScope,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
@ -86,14 +82,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) } val ongoingSendAttachmentJob = remember { mutableStateOf<Job?>(null) }
val allowCaption by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionCreation)
}.collectAsState(initial = false)
val showCaptionCompatibilityWarning by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaCaptionWarning)
}.collectAsState(initial = false)
var useSendQueue by remember { mutableStateOf(false) }
var preprocessMediaJob by remember { mutableStateOf<Job?>(null) } var preprocessMediaJob by remember { mutableStateOf<Job?>(null) }
val mediaAttachment = attachment as Attachment.Media val mediaAttachment = attachment as Attachment.Media
@ -106,10 +94,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
var displayFileTooLargeError by remember { mutableStateOf(false) } var displayFileTooLargeError by remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
useSendQueue = featureFlagService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
}
LaunchedEffect(mediaOptimizationSelectorState.displayMediaSelectorViews) { LaunchedEffect(mediaOptimizationSelectorState.displayMediaSelectorViews) {
// If the media optimization selector is not displayed, we can pre-process the media // If the media optimization selector is not displayed, we can pre-process the media
// to prepare it for sending. This is done to avoid blocking the UI thread when the // to prepare it for sending. This is done to avoid blocking the UI thread when the
@ -182,18 +166,17 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
.takeIf { it.isNotEmpty() } .takeIf { it.isNotEmpty() }
// If we're supposed to send the media as a background job, we can dismiss this screen already // If we're supposed to send the media as a background job, we can dismiss this screen already
if (useSendQueue && coroutineContext.isActive) { if (coroutineContext.isActive) {
onDoneListener() onDoneListener()
} }
// If using the send queue, send it using the session coroutine scope so it doesn't matter if this screen or the chat one are closed // Send the media using the session coroutine scope so it doesn't matter if this screen or the chat one are closed
val sendMediaCoroutineScope = if (useSendQueue) sessionCoroutineScope else coroutineScope sessionCoroutineScope.launch(dispatchers.io) {
sendMediaCoroutineScope.launch(dispatchers.io) {
sendPreProcessedMedia( sendPreProcessedMedia(
mediaUploadInfo = mediaUploadInfo, mediaUploadInfo = mediaUploadInfo,
caption = caption, caption = caption,
sendActionState = sendActionState, sendActionState = sendActionState,
dismissAfterSend = !useSendQueue, dismissAfterSend = false,
inReplyToEventId = null, inReplyToEventId = null,
) )
@ -238,8 +221,6 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
attachment = attachment, attachment = attachment,
sendActionState = sendActionState.value, sendActionState = sendActionState.value,
textEditorState = textEditorState, textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState, mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError, displayFileTooLargeError = displayFileTooLargeError,
eventSink = ::handleEvents eventSink = ::handleEvents

View file

@ -17,8 +17,6 @@ data class AttachmentsPreviewState(
val attachment: Attachment, val attachment: Attachment,
val sendActionState: SendActionState, val sendActionState: SendActionState,
val textEditorState: TextEditorState, val textEditorState: TextEditorState,
val allowCaption: Boolean,
val showCaptionCompatibilityWarning: Boolean,
val mediaOptimizationSelectorState: MediaOptimizationSelectorState, val mediaOptimizationSelectorState: MediaOptimizationSelectorState,
val displayFileTooLargeError: Boolean, val displayFileTooLargeError: Boolean,
val eventSink: (AttachmentsPreviewEvents) -> Unit val eventSink: (AttachmentsPreviewEvents) -> Unit

View file

@ -41,8 +41,6 @@ open class AttachmentsPreviewStateProvider : PreviewParameterProvider<Attachment
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.ReadyToUpload(aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Sending.ReadyToUpload(aMediaUploadInfo())),
anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f, aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Sending.Uploading(0.5f, aMediaUploadInfo())),
anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"), aMediaUploadInfo())), anAttachmentsPreviewState(sendActionState = SendActionState.Failure(RuntimeException("error"), aMediaUploadInfo())),
anAttachmentsPreviewState(allowCaption = false),
anAttachmentsPreviewState(showCaptionCompatibilityWarning = true),
anAttachmentsPreviewState(displayFileTooLargeError = true), anAttachmentsPreviewState(displayFileTooLargeError = true),
anAttachmentsPreviewState( anAttachmentsPreviewState(
mediaInfo = aVideoMediaInfo(), mediaInfo = aVideoMediaInfo(),
@ -65,8 +63,6 @@ fun anAttachmentsPreviewState(
mediaInfo: MediaInfo = anImageMediaInfo(), mediaInfo: MediaInfo = anImageMediaInfo(),
textEditorState: TextEditorState = aTextEditorStateMarkdown(), textEditorState: TextEditorState = aTextEditorStateMarkdown(),
sendActionState: SendActionState = SendActionState.Idle, sendActionState: SendActionState = SendActionState.Idle,
allowCaption: Boolean = true,
showCaptionCompatibilityWarning: Boolean = true,
mediaOptimizationSelectorState: MediaOptimizationSelectorState = aMediaOptimisationSelectorState(), mediaOptimizationSelectorState: MediaOptimizationSelectorState = aMediaOptimisationSelectorState(),
displayFileTooLargeError: Boolean = false, displayFileTooLargeError: Boolean = false,
) = AttachmentsPreviewState( ) = AttachmentsPreviewState(
@ -75,8 +71,6 @@ fun anAttachmentsPreviewState(
), ),
sendActionState = sendActionState, sendActionState = sendActionState,
textEditorState = textEditorState, textEditorState = textEditorState,
allowCaption = allowCaption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
mediaOptimizationSelectorState = mediaOptimizationSelectorState, mediaOptimizationSelectorState = mediaOptimizationSelectorState,
displayFileTooLargeError = displayFileTooLargeError, displayFileTooLargeError = displayFileTooLargeError,
eventSink = {} eventSink = {}

View file

@ -363,17 +363,13 @@ private fun AttachmentsPreviewBottomActions(
modifier = modifier, modifier = modifier,
state = state.textEditorState, state = state.textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Attachment( composerMode = MessageComposerMode.Attachment,
allowCaption = state.allowCaption,
showCaptionCompatibilityWarning = state.showCaptionCompatibilityWarning,
),
onRequestFocus = {}, onRequestFocus = {},
onSendMessage = onSendClick, onSendMessage = onSendClick,
showTextFormatting = false, showTextFormatting = false,
onResetComposerMode = {}, onResetComposerMode = {},
onAddAttachment = {}, onAddAttachment = {},
onDismissTextFormatting = {}, onDismissTextFormatting = {},
enableVoiceMessages = false,
onVoiceRecorderEvent = {}, onVoiceRecorderEvent = {},
onVoicePlayerEvent = {}, onVoicePlayerEvent = {},
onSendVoiceMessage = {}, onSendVoiceMessage = {},

View file

@ -132,17 +132,15 @@ private fun AttachmentSourcePickerMenu(
style = ListItemStyle.Primary, style = ListItemStyle.Primary,
) )
} }
if (state.canCreatePoll) { ListItem(
ListItem( modifier = Modifier.clickable {
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll)
state.eventSink(MessageComposerEvents.PickAttachmentSource.Poll) onCreatePollClick()
onCreatePollClick() },
}, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Polls())),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Polls())), headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_poll)) },
headlineContent = { Text(stringResource(R.string.screen_room_attachment_source_poll)) }, style = ListItemStyle.Primary,
style = ListItemStyle.Primary, )
)
}
if (enableTextFormatting) { if (enableTextFormatting) {
ListItem( ListItem(
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) }, modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.ToggleTextFormatting(enabled = true)) },

View file

@ -45,8 +45,6 @@ import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
import io.element.android.libraries.di.annotations.SessionCoroutineScope import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
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.permalink.PermalinkBuilder import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
@ -103,7 +101,6 @@ class MessageComposerPresenter @AssistedInject constructor(
private val sessionCoroutineScope: CoroutineScope, private val sessionCoroutineScope: CoroutineScope,
private val room: JoinedRoom, private val room: JoinedRoom,
private val mediaPickerProvider: PickerProvider, private val mediaPickerProvider: PickerProvider,
private val featureFlagService: FeatureFlagService,
private val sessionPreferencesStore: SessionPreferencesStore, private val sessionPreferencesStore: SessionPreferencesStore,
private val localMediaFactory: LocalMediaFactory, private val localMediaFactory: LocalMediaFactory,
private val mediaSender: MediaSender, private val mediaSender: MediaSender,
@ -156,13 +153,7 @@ class MessageComposerPresenter @AssistedInject constructor(
val canShareLocation = remember { mutableStateOf(false) } val canShareLocation = remember { mutableStateOf(false) }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing) && canShareLocation.value = locationService.isServiceAvailable()
locationService.isServiceAvailable()
}
val canCreatePoll = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
canCreatePoll.value = featureFlagService.isFeatureEnabled(FeatureFlags.Polls)
} }
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType -> val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType ->
@ -376,7 +367,6 @@ class MessageComposerPresenter @AssistedInject constructor(
showAttachmentSourcePicker = showAttachmentSourcePicker, showAttachmentSourcePicker = showAttachmentSourcePicker,
showTextFormatting = showTextFormatting, showTextFormatting = showTextFormatting,
canShareLocation = canShareLocation.value, canShareLocation = canShareLocation.value,
canCreatePoll = canCreatePoll.value,
suggestions = suggestions.toPersistentList(), suggestions = suggestions.toPersistentList(),
resolveMentionDisplay = resolveMentionDisplay, resolveMentionDisplay = resolveMentionDisplay,
resolveAtRoomMentionDisplay = resolveAtRoomMentionDisplay, resolveAtRoomMentionDisplay = resolveAtRoomMentionDisplay,

View file

@ -22,7 +22,6 @@ data class MessageComposerState(
val showAttachmentSourcePicker: Boolean, val showAttachmentSourcePicker: Boolean,
val showTextFormatting: Boolean, val showTextFormatting: Boolean,
val canShareLocation: Boolean, val canShareLocation: Boolean,
val canCreatePoll: Boolean,
val suggestions: ImmutableList<ResolvedSuggestion>, val suggestions: ImmutableList<ResolvedSuggestion>,
val resolveMentionDisplay: (String, String) -> TextDisplay, val resolveMentionDisplay: (String, String) -> TextDisplay,
val resolveAtRoomMentionDisplay: () -> TextDisplay, val resolveAtRoomMentionDisplay: () -> TextDisplay,

View file

@ -30,7 +30,6 @@ fun aMessageComposerState(
showTextFormatting: Boolean = false, showTextFormatting: Boolean = false,
showAttachmentSourcePicker: Boolean = false, showAttachmentSourcePicker: Boolean = false,
canShareLocation: Boolean = true, canShareLocation: Boolean = true,
canCreatePoll: Boolean = true,
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(), suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
eventSink: (MessageComposerEvents) -> Unit = {}, eventSink: (MessageComposerEvents) -> Unit = {},
) = MessageComposerState( ) = MessageComposerState(
@ -40,7 +39,6 @@ fun aMessageComposerState(
showTextFormatting = showTextFormatting, showTextFormatting = showTextFormatting,
showAttachmentSourcePicker = showAttachmentSourcePicker, showAttachmentSourcePicker = showAttachmentSourcePicker,
canShareLocation = canShareLocation, canShareLocation = canShareLocation,
canCreatePoll = canCreatePoll,
suggestions = suggestions, suggestions = suggestions,
resolveMentionDisplay = { _, _ -> TextDisplay.Plain }, resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
resolveAtRoomMentionDisplay = { TextDisplay.Plain }, resolveAtRoomMentionDisplay = { TextDisplay.Plain },

View file

@ -33,7 +33,6 @@ import kotlinx.coroutines.launch
internal fun MessageComposerView( internal fun MessageComposerView(
state: MessageComposerState, state: MessageComposerState,
voiceMessageState: VoiceMessageComposerState, voiceMessageState: VoiceMessageComposerState,
enableVoiceMessages: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val view = LocalView.current val view = LocalView.current
@ -104,7 +103,6 @@ internal fun MessageComposerView(
onResetComposerMode = ::onCloseSpecialMode, onResetComposerMode = ::onCloseSpecialMode,
onAddAttachment = ::onAddAttachment, onAddAttachment = ::onAddAttachment,
onDismissTextFormatting = ::onDismissTextFormatting, onDismissTextFormatting = ::onDismissTextFormatting,
enableVoiceMessages = enableVoiceMessages,
onVoiceRecorderEvent = onVoiceRecorderEvent, onVoiceRecorderEvent = onVoiceRecorderEvent,
onVoicePlayerEvent = onVoicePlayerEvent, onVoicePlayerEvent = onVoicePlayerEvent,
onSendVoiceMessage = onSendVoiceMessage, onSendVoiceMessage = onSendVoiceMessage,
@ -128,13 +126,11 @@ internal fun MessageComposerViewPreview(
modifier = Modifier.height(IntrinsicSize.Min), modifier = Modifier.height(IntrinsicSize.Min),
state = state, state = state,
voiceMessageState = aVoiceMessageComposerState(), voiceMessageState = aVoiceMessageComposerState(),
enableVoiceMessages = true,
) )
MessageComposerView( MessageComposerView(
modifier = Modifier.height(200.dp), modifier = Modifier.height(200.dp),
state = state, state = state,
voiceMessageState = aVoiceMessageComposerState(), voiceMessageState = aVoiceMessageComposerState(),
enableVoiceMessages = true,
) )
DisabledComposerView() DisabledComposerView()
} }
@ -150,7 +146,6 @@ internal fun MessageComposerViewVoicePreview(
modifier = Modifier.height(IntrinsicSize.Min), modifier = Modifier.height(IntrinsicSize.Min),
state = aMessageComposerState(), state = aMessageComposerState(),
voiceMessageState = state, voiceMessageState = state,
enableVoiceMessages = true,
) )
} }
} }

View file

@ -1,41 +0,0 @@
/*
* Copyright 2024 New Vector 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.pinned
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultIsPinnedMessagesFeatureEnabled @Inject constructor(
private val featureFlagService: FeatureFlagService,
) : IsPinnedMessagesFeatureEnabled {
@Composable
override operator fun invoke(): Boolean {
var isFeatureEnabled by rememberSaveable {
mutableStateOf(false)
}
LaunchedEffect(Unit) {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents)
.onEach { isFeatureEnabled = it }
.launchIn(this)
}
return isFeatureEnabled
}
}

View file

@ -12,8 +12,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.mapState import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.di.SingleIn import io.element.android.libraries.di.SingleIn
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.room.CreateTimelineParams import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
@ -23,7 +21,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -35,7 +32,6 @@ import javax.inject.Inject
class PinnedEventsTimelineProvider @Inject constructor( class PinnedEventsTimelineProvider @Inject constructor(
private val room: JoinedRoom, private val room: JoinedRoom,
private val syncService: SyncService, private val syncService: SyncService,
private val featureFlagService: FeatureFlagService,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
) : TimelineProvider { ) : TimelineProvider {
private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> = private val _timelineStateFlow: MutableStateFlow<AsyncData<Timeline>> =
@ -66,20 +62,10 @@ class PinnedEventsTimelineProvider @Inject constructor(
} }
private suspend fun onActive() = coroutineScope { private suspend fun onActive() = coroutineScope {
combine( syncService.syncState.onEach {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents),
syncService.syncState,
) { isEnabled, _ ->
// do not use syncState here as data can be loaded from cache, it's just to trigger retry if needed // do not use syncState here as data can be loaded from cache, it's just to trigger retry if needed
isEnabled loadTimelineIfNeeded()
} }
.onEach { isFeatureEnabled ->
if (isFeatureEnabled) {
loadTimelineIfNeeded()
} else {
resetTimeline()
}
}
.launchIn(this) .launchIn(this)
} }

View file

@ -28,8 +28,6 @@ import io.element.android.features.messages.impl.utils.TextPillificationHelper
import io.element.android.libraries.androidutils.filesize.FileSizeFormatter import io.element.android.libraries.androidutils.filesize.FileSizeFormatter
import io.element.android.libraries.androidutils.text.safeLinkify import io.element.android.libraries.androidutils.text.safeLinkify
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
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.permalink.PermalinkParser import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType import io.element.android.libraries.matrix.api.timeline.item.event.AudioMessageType
@ -56,7 +54,6 @@ import kotlin.time.Duration
class TimelineItemContentMessageFactory @Inject constructor( class TimelineItemContentMessageFactory @Inject constructor(
private val fileSizeFormatter: FileSizeFormatter, private val fileSizeFormatter: FileSizeFormatter,
private val fileExtensionExtractor: FileExtensionExtractor, private val fileExtensionExtractor: FileExtensionExtractor,
private val featureFlagService: FeatureFlagService,
private val htmlConverterProvider: HtmlConverterProvider, private val htmlConverterProvider: HtmlConverterProvider,
private val permalinkParser: PermalinkParser, private val permalinkParser: PermalinkParser,
private val textPillificationHelper: TextPillificationHelper, private val textPillificationHelper: TextPillificationHelper,
@ -177,38 +174,20 @@ class TimelineItemContentMessageFactory @Inject constructor(
) )
} }
is VoiceMessageType -> { is VoiceMessageType -> {
when (featureFlagService.isFeatureEnabled(FeatureFlags.VoiceMessages)) { TimelineItemVoiceContent(
true -> { eventId = eventId,
TimelineItemVoiceContent( filename = messageType.filename,
eventId = eventId, fileSize = messageType.info?.size ?: 0,
filename = messageType.filename, caption = messageType.caption?.trimEnd(),
fileSize = messageType.info?.size ?: 0, formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
caption = messageType.caption?.trimEnd(), isEdited = content.isEdited,
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(), mediaSource = messageType.source,
isEdited = content.isEdited, duration = messageType.info?.duration ?: Duration.ZERO,
mediaSource = messageType.source, mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
duration = messageType.info?.duration ?: Duration.ZERO, waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(),
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream, formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
waveform = messageType.details?.waveform?.toImmutableList() ?: persistentListOf(), fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0), )
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)
)
}
false -> {
TimelineItemAudioContent(
filename = messageType.filename,
fileSize = messageType.info?.size ?: 0,
caption = messageType.caption?.trimEnd(),
formattedCaption = parseHtml(messageType.formattedCaption) ?: messageType.caption?.withLinks(),
isEdited = content.isEdited,
mediaSource = messageType.source,
duration = messageType.info?.duration ?: Duration.ZERO,
mimeType = messageType.info?.mimetype ?: MimeTypes.OctetStream,
formattedFileSize = fileSizeFormatter.format(messageType.info?.size ?: 0),
fileExtension = fileExtensionExtractor.extractFromName(messageType.filename),
)
}
}
} }
is FileMessageType -> { is FileMessageType -> {
val fileExtension = fileExtensionExtractor.extractFromName(messageType.filename) val fileExtension = fileExtensionExtractor.extractFromName(messageType.filename)

View file

@ -9,23 +9,18 @@ package io.element.android.features.messages.impl.timeline.factories.event
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemUnknownContent
import io.element.android.features.poll.api.pollcontent.PollContentStateFactory import io.element.android.features.poll.api.pollcontent.PollContentStateFactory
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import javax.inject.Inject import javax.inject.Inject
class TimelineItemContentPollFactory @Inject constructor( class TimelineItemContentPollFactory @Inject constructor(
private val featureFlagService: FeatureFlagService,
private val pollContentStateFactory: PollContentStateFactory, private val pollContentStateFactory: PollContentStateFactory,
) { ) {
suspend fun create( suspend fun create(
event: EventTimelineItem, event: EventTimelineItem,
content: PollContent, content: PollContent,
): TimelineItemEventContent { ): TimelineItemEventContent {
if (!featureFlagService.isFeatureEnabled(FeatureFlags.Polls)) return TimelineItemUnknownContent
val pollContentState = pollContentStateFactory.create(event, content) val pollContentState = pollContentStateFactory.create(event, content)
return TimelineItemPollContent( return TimelineItemPollContent(
isMine = pollContentState.isMine, isMine = pollContentState.isMine,

View file

@ -46,9 +46,6 @@ import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarData
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
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
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
@ -994,37 +991,6 @@ class MessagesPresenterTest {
composerMode = MessageComposerMode.EditCaption( composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = A_CAPTION, content = A_CAPTION,
showCaptionCompatibilityWarning = true,
)
)
)
}
}
@Test
fun `present - handle action edit caption without warning`() = runTest {
val messageEvent = aMessageEvent(
content = aTimelineItemImageContent(
caption = A_CAPTION,
)
)
val composerRecorder = EventsRecorder<MessageComposerEvents>()
val presenter = createMessagesPresenter(
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.MediaCaptionWarning.key to false)
)
)
presenter.testWithLifecycleOwner {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent))
awaitItem()
composerRecorder.assertSingle(
MessageComposerEvents.SetMode(
composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = A_CAPTION,
showCaptionCompatibilityWarning = false,
) )
) )
) )
@ -1051,37 +1017,6 @@ class MessagesPresenterTest {
composerMode = MessageComposerMode.EditCaption( composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = "", content = "",
showCaptionCompatibilityWarning = true,
)
)
)
}
}
@Test
fun `present - handle action add caption without warning`() = runTest {
val composerRecorder = EventsRecorder<MessageComposerEvents>()
val presenter = createMessagesPresenter(
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.MediaCaptionWarning.key to false)
)
)
val messageEvent = aMessageEvent(
content = aTimelineItemImageContent(
caption = null,
)
)
presenter.testWithLifecycleOwner {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent))
awaitItem()
composerRecorder.assertSingle(
MessageComposerEvents.SetMode(
composerMode = MessageComposerMode.EditCaption(
eventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
content = "",
showCaptionCompatibilityWarning = false,
) )
) )
) )
@ -1234,7 +1169,6 @@ class MessagesPresenterTest {
typingNoticeResult = { Result.success(Unit) }, typingNoticeResult = { Result.success(Unit) },
), ),
navigator: FakeMessagesNavigator = FakeMessagesNavigator(), navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
timelineEventSink: (TimelineEvents) -> Unit = {}, timelineEventSink: (TimelineEvents) -> Unit = {},
@ -1270,7 +1204,6 @@ class MessagesPresenterTest {
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
navigator = navigator, navigator = navigator,
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,
featureFlagsService = featureFlagService,
buildMeta = aBuildMeta(), buildMeta = aBuildMeta(),
dispatchers = coroutineDispatchers, dispatchers = coroutineDispatchers,
htmlConverterProvider = FakeHtmlConverterProvider(), htmlConverterProvider = FakeHtmlConverterProvider(),

View file

@ -27,8 +27,6 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList import io.element.android.features.poll.api.pollcontent.aPollAnswerItemList
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.room.BaseRoom import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
@ -52,7 +50,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -63,7 +61,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for message from me redacted`() = runTest { fun `present - compute for message from me redacted`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -100,7 +98,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for message from others redacted`() = runTest { fun `present - compute for message from others redacted`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -141,7 +139,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message`() = runTest { fun `present - compute for others message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -188,7 +186,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message in a thread`() = runTest { fun `present - compute for others message in a thread`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
@ -234,7 +232,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message cannot sent message`() = runTest { fun `present - compute for others message cannot sent message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -280,7 +278,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message and can redact`() = runTest { fun `present - compute for others message and can redact`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -328,7 +326,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message and cannot send reaction`() = runTest { fun `present - compute for others message and cannot send reaction`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -376,7 +374,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message`() = runTest { fun `present - compute for my message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -423,7 +421,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message in a thread`() = runTest { fun `present - compute for my message in a thread`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
@ -469,7 +467,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message cannot redact`() = runTest { fun `present - compute for my message cannot redact`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -515,7 +513,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message no permission`() = runTest { fun `present - compute for my message no permission`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -558,7 +556,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a media item`() = runTest { fun `present - compute for a media item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -603,61 +601,9 @@ class ActionListPresenterTest {
} }
} }
@Test
fun `present - compute for a media item - caption disabled`() = runTest {
val presenter = createActionListPresenter(
isDeveloperModeEnabled = true,
isPinFeatureEnabled = true,
allowCaption = false,
)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
val messageEvent = aMessageEvent(
isMine = true,
isEditable = true,
content = aTimelineItemImageContent(),
)
initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage(
event = messageEvent,
userEventPermissions = aUserEventPermissions(
canRedactOwn = true,
canRedactOther = false,
canSendMessage = true,
canSendReaction = true,
canPinUnpin = true,
),
)
)
val successState = awaitItem()
assertThat(successState.target).isEqualTo(
ActionListState.Target.Success(
event = messageEvent,
sentTimeFull = "0 Full true",
displayEmojiReactions = true,
verifiedUserSendFailure = VerifiedUserSendFailure.None,
actions = persistentListOf(
TimelineItemAction.Reply,
TimelineItemAction.Forward,
// Not here
// TimelineItemAction.AddCaption,
TimelineItemAction.CopyLink,
TimelineItemAction.Pin,
TimelineItemAction.ViewSource,
TimelineItemAction.Redact,
)
)
)
initialState.eventSink.invoke(ActionListEvents.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
}
}
@Test @Test
fun `present - compute for a media with caption item`() = runTest { fun `present - compute for a media with caption item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -708,7 +654,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a media with caption item - other user event`() = runTest { fun `present - compute for a media with caption item - other user event`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -757,7 +703,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a state item in debug build`() = runTest { fun `present - compute for a state item in debug build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -797,7 +743,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a state item in non-debuggable build`() = runTest { fun `present - compute for a state item in non-debuggable build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -824,7 +770,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message in non-debuggable build`() = runTest { fun `present - compute message in non-debuggable build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -870,7 +816,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message when user can't pin`() = runTest { fun `present - compute message when user can't pin`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -921,7 +867,6 @@ class ActionListPresenterTest {
} }
val presenter = createActionListPresenter( val presenter = createActionListPresenter(
isDeveloperModeEnabled = true, isDeveloperModeEnabled = true,
isPinFeatureEnabled = true,
room = room room = room
) )
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -970,7 +915,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message with no actions`() = runTest { fun `present - compute message with no actions`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1017,7 +962,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute not sent message`() = runTest { fun `present - compute not sent message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1061,7 +1006,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for editable poll message`() = runTest { fun `present - compute for editable poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1105,7 +1050,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for non-editable poll message`() = runTest { fun `present - compute for non-editable poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1148,7 +1093,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for ended poll message`() = runTest { fun `present - compute for ended poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1190,7 +1135,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for voice message`() = runTest { fun `present - compute for voice message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1235,7 +1180,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for call notify`() = runTest { fun `present - compute for call notify`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true, isPinFeatureEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1275,7 +1220,7 @@ class ActionListPresenterTest {
val room = FakeBaseRoom( val room = FakeBaseRoom(
userDisplayNameResult = { Result.success("Alice") } userDisplayNameResult = { Result.success("Alice") }
) )
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, isPinFeatureEnabled = false, room = room) val presenter = createActionListPresenter(isDeveloperModeEnabled = false, room = room)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
@ -1299,22 +1244,14 @@ class ActionListPresenterTest {
private fun createActionListPresenter( private fun createActionListPresenter(
isDeveloperModeEnabled: Boolean, isDeveloperModeEnabled: Boolean,
isPinFeatureEnabled: Boolean,
room: BaseRoom = FakeBaseRoom(), room: BaseRoom = FakeBaseRoom(),
allowCaption: Boolean = true,
): ActionListPresenter { ): ActionListPresenter {
val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled)
return DefaultActionListPresenter( return DefaultActionListPresenter(
postProcessor = TimelineItemActionPostProcessor.Default, postProcessor = TimelineItemActionPostProcessor.Default,
appPreferencesStore = preferencesStore, appPreferencesStore = preferencesStore,
isPinnedMessagesFeatureEnabled = { isPinFeatureEnabled },
room = room, room = room,
userSendFailureFactory = VerifiedUserSendFailureFactory(room), userSendFailureFactory = VerifiedUserSendFailureFactory(room),
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.MediaCaptionCreation.key to allowCaption,
),
),
dateFormatter = FakeDateFormatter(), dateFormatter = FakeDateFormatter(),
) )
} }

View file

@ -25,8 +25,6 @@ import io.element.android.features.messages.test.attachments.video.FakeMediaOpti
import io.element.android.libraries.androidutils.file.TemporaryUriDeleter import io.element.android.libraries.androidutils.file.TemporaryUriDeleter
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
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.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.AudioInfo
@ -84,34 +82,8 @@ class AttachmentsPreviewPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
createAttachmentsPreviewPresenter().test { createAttachmentsPreviewPresenter().test {
skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(initialState.allowCaption).isTrue()
assertThat(initialState.showCaptionCompatibilityWarning).isTrue()
}
}
@Test
fun `present - initial state no caption warning`() = runTest {
createAttachmentsPreviewPresenter(
showCaptionCompatibilityWarning = false,
).test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showCaptionCompatibilityWarning).isFalse()
}
}
@Test
fun `present - initial state - caption not allowed`() = runTest {
createAttachmentsPreviewPresenter(
allowCaption = false,
).test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(initialState.allowCaption).isFalse()
} }
} }
@ -144,7 +116,6 @@ class AttachmentsPreviewPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true))
@ -186,7 +157,6 @@ class AttachmentsPreviewPresenterTest {
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
@ -221,7 +191,6 @@ class AttachmentsPreviewPresenterTest {
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
// Pre-processing finishes // Pre-processing finishes
@ -253,7 +222,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
@ -282,7 +250,6 @@ class AttachmentsPreviewPresenterTest {
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java)
} }
@ -304,7 +271,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
deleteCallback.assertions().isCalledOnce() deleteCallback.assertions().isCalledOnce()
onDoneListener.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce()
@ -339,7 +305,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -383,7 +348,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -425,7 +389,6 @@ class AttachmentsPreviewPresenterTest {
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -442,38 +405,7 @@ class AttachmentsPreviewPresenterTest {
} }
@Test @Test
fun `present - send media failure scenario without media queue`() = runTest { fun `present - send media failure scenario`() = runTest {
val failure = MediaPreProcessor.Failure(null)
val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
Result.failure(failure)
}
val room = FakeJoinedRoom(
liveTimeline = FakeTimeline().apply {
sendFileLambda = sendFileResult
},
)
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = false)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure, mediaUploadInfo))
sendFileResult.assertions().isCalledOnce()
failureState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
val clearedState = awaitLastSequentialItem()
assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
}
}
@Test
fun `present - send media failure scenario with media queue`() = runTest {
val failure = MediaPreProcessor.Failure(null) val failure = MediaPreProcessor.Failure(null)
val sendFileResult = val sendFileResult =
lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ -> lambdaRecorder<File, FileInfo, String?, String?, ProgressCallback?, EventId?, Result<FakeMediaUploadHandler>> { _, _, _, _, _, _ ->
@ -485,14 +417,13 @@ class AttachmentsPreviewPresenterTest {
sendFileLambda = sendFileResult sendFileLambda = sendFileResult
}, },
) )
val presenter = createAttachmentsPreviewPresenter(room = room, mediaUploadOnSendQueueEnabled = true, onDoneListener = onDoneListenerResult) val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult)
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
presenter.present() presenter.present()
}.test { }.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
@ -509,25 +440,7 @@ class AttachmentsPreviewPresenterTest {
} }
@Test @Test
fun `present - dismissing the progress dialog stops media upload without media queue`() = runTest { fun `present - dismissing the progress dialog stops media upload`() = runTest {
val presenter = createAttachmentsPreviewPresenter(mediaUploadOnSendQueueEnabled = false)
moleculeFlow(RecompositionMode.Immediate) {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
// The sending is cancelled and the state is kept at ReadyToUpload
ensureAllEventsConsumed()
}
}
@Test
fun `present - dismissing the progress dialog stops media upload with media queue`() = runTest {
val onDoneListenerResult = lambdaRecorder<Unit> {} val onDoneListenerResult = lambdaRecorder<Unit> {}
val presenter = createAttachmentsPreviewPresenter( val presenter = createAttachmentsPreviewPresenter(
room = FakeJoinedRoom( room = FakeJoinedRoom(
@ -537,7 +450,6 @@ class AttachmentsPreviewPresenterTest {
} }
} }
), ),
mediaUploadOnSendQueueEnabled = true,
onDoneListener = onDoneListenerResult, onDoneListener = onDoneListenerResult,
) )
moleculeFlow(RecompositionMode.Immediate) { moleculeFlow(RecompositionMode.Immediate) {
@ -546,7 +458,6 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvents.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState)
@ -574,7 +485,6 @@ class AttachmentsPreviewPresenterTest {
} }
} }
), ),
mediaUploadOnSendQueueEnabled = true,
onDoneListener = onDoneListenerResult, onDoneListener = onDoneListenerResult,
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory { mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
MediaOptimizationSelectorState( MediaOptimizationSelectorState(
@ -616,7 +526,6 @@ class AttachmentsPreviewPresenterTest {
} }
} }
), ),
mediaUploadOnSendQueueEnabled = true,
onDoneListener = onDoneListenerResult, onDoneListener = onDoneListenerResult,
mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory { mediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory {
MediaOptimizationSelectorState( MediaOptimizationSelectorState(
@ -672,9 +581,6 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(), mediaPreProcessor: MediaPreProcessor = FakeMediaPreProcessor(),
temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(), temporaryUriDeleter: TemporaryUriDeleter = FakeTemporaryUriDeleter(),
onDoneListener: OnDoneListener = OnDoneListener { lambdaError() }, onDoneListener: OnDoneListener = OnDoneListener { lambdaError() },
mediaUploadOnSendQueueEnabled: Boolean = true,
allowCaption: Boolean = true,
showCaptionCompatibilityWarning: Boolean = true,
displayMediaQualitySelectorViews: Boolean = false, displayMediaQualitySelectorViews: Boolean = false,
mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory( mediaOptimizationSelectorPresenterFactory: FakeMediaOptimizationSelectorPresenterFactory = FakeMediaOptimizationSelectorPresenterFactory(
fakePresenter = { fakePresenter = {
@ -698,13 +604,6 @@ class AttachmentsPreviewPresenterTest {
}), }),
permalinkBuilder = permalinkBuilder, permalinkBuilder = permalinkBuilder,
temporaryUriDeleter = temporaryUriDeleter, temporaryUriDeleter = temporaryUriDeleter,
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.MediaUploadOnSendQueue.key to mediaUploadOnSendQueueEnabled,
FeatureFlags.MediaCaptionCreation.key to allowCaption,
FeatureFlags.MediaCaptionWarning.key to showCaptionCompatibilityWarning,
),
),
sessionCoroutineScope = this, sessionCoroutineScope = this,
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory, mediaOptimizationSelectorPresenterFactory = mediaOptimizationSelectorPresenterFactory,

View file

@ -30,7 +30,6 @@ import io.element.android.features.poll.test.pollcontent.FakePollContentStateFac
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
import io.element.android.libraries.eventformatter.api.TimelineEventFormatter import io.element.android.libraries.eventformatter.api.TimelineEventFormatter
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
@ -60,7 +59,6 @@ internal fun TestScope.aTimelineItemsFactory(
messageFactory = TimelineItemContentMessageFactory( messageFactory = TimelineItemContentMessageFactory(
fileSizeFormatter = FakeFileSizeFormatter(), fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = FakeFeatureFlagService(),
htmlConverterProvider = FakeHtmlConverterProvider(), htmlConverterProvider = FakeHtmlConverterProvider(),
permalinkParser = FakePermalinkParser(), permalinkParser = FakePermalinkParser(),
textPillificationHelper = FakeTextPillificationHelper(), textPillificationHelper = FakeTextPillificationHelper(),
@ -70,7 +68,7 @@ internal fun TestScope.aTimelineItemsFactory(
fileSizeFormatter = FakeFileSizeFormatter(), fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation() fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
), ),
pollFactory = TimelineItemContentPollFactory(FakeFeatureFlagService(), FakePollContentStateFactory()), pollFactory = TimelineItemContentPollFactory(FakePollContentStateFactory()),
utdFactory = TimelineItemContentUTDFactory(), utdFactory = TimelineItemContentUTDFactory(),
roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter), roomMembershipFactory = TimelineItemContentRoomMembershipFactory(timelineEventFormatter),
profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter), profileChangeFactory = TimelineItemContentProfileChangeFactory(timelineEventFormatter),

View file

@ -32,9 +32,6 @@ import io.element.android.features.messages.impl.utils.FakeTextPillificationHelp
import io.element.android.features.messages.impl.utils.TextPillificationHelper import io.element.android.features.messages.impl.utils.TextPillificationHelper
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
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
import io.element.android.libraries.matrix.api.core.ThreadId import io.element.android.libraries.matrix.api.core.ThreadId
@ -125,9 +122,6 @@ class MessageComposerPresenterTest {
private val pickerProvider = FakePickerProvider().apply { private val pickerProvider = FakePickerProvider().apply {
givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk
} }
private val featureFlagService = FakeFeatureFlagService(
mapOf(FeatureFlags.LocationSharing.key to true)
)
private val mediaPreProcessor = FakeMediaPreProcessor() private val mediaPreProcessor = FakeMediaPreProcessor()
private val snackbarDispatcher = SnackbarDispatcher() private val snackbarDispatcher = SnackbarDispatcher()
private val mockMediaUrl: Uri = mockk("localMediaUri") private val mockMediaUrl: Uri = mockk("localMediaUri")
@ -1529,7 +1523,6 @@ class MessageComposerPresenterTest {
), ),
navigator: MessagesNavigator = FakeMessagesNavigator(), navigator: MessagesNavigator = FakeMessagesNavigator(),
pickerProvider: PickerProvider = this@MessageComposerPresenterTest.pickerProvider, pickerProvider: PickerProvider = this@MessageComposerPresenterTest.pickerProvider,
featureFlagService: FeatureFlagService = this@MessageComposerPresenterTest.featureFlagService,
locationService: LocationService = FakeLocationService(true), locationService: LocationService = FakeLocationService(true),
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
mediaPreProcessor: MediaPreProcessor = this@MessageComposerPresenterTest.mediaPreProcessor, mediaPreProcessor: MediaPreProcessor = this@MessageComposerPresenterTest.mediaPreProcessor,
@ -1551,7 +1544,6 @@ class MessageComposerPresenterTest {
sessionCoroutineScope = this, sessionCoroutineScope = this,
room = room, room = room,
mediaPickerProvider = pickerProvider, mediaPickerProvider = pickerProvider,
featureFlagService = featureFlagService,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
localMediaFactory = localMediaFactory, localMediaFactory = localMediaFactory,
mediaSender = MediaSender( mediaSender = MediaSender(
@ -1593,11 +1585,9 @@ fun anEditMode(
fun anEditCaptionMode( fun anEditCaptionMode(
eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(), eventOrTransactionId: EventOrTransactionId = AN_EVENT_ID.toEventOrTransactionId(),
caption: String = A_CAPTION, caption: String = A_CAPTION,
showCaptionCompatibilityWarning: Boolean = false,
) = MessageComposerMode.EditCaption( ) = MessageComposerMode.EditCaption(
eventOrTransactionId = eventOrTransactionId, eventOrTransactionId = eventOrTransactionId,
content = caption, content = caption,
showCaptionCompatibilityWarning = showCaptionCompatibilityWarning,
) )
fun aReplyMode() = MessageComposerMode.Reply( fun aReplyMode() = MessageComposerMode.Reply(

View file

@ -10,8 +10,6 @@ package io.element.android.features.messages.impl.pinned.banner
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider
import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter
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.JoinedRoom
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
@ -35,7 +33,7 @@ import org.junit.Test
class PinnedMessagesBannerPresenterTest { class PinnedMessagesBannerPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createPinnedMessagesBannerPresenter(isFeatureEnabled = true) val presenter = createPinnedMessagesBannerPresenter()
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState).isEqualTo(PinnedMessagesBannerState.Hidden) assertThat(initialState).isEqualTo(PinnedMessagesBannerState.Hidden)
@ -43,15 +41,6 @@ class PinnedMessagesBannerPresenterTest {
} }
} }
@Test
fun `present - feature disabled`() = runTest {
val presenter = createPinnedMessagesBannerPresenter(isFeatureEnabled = false)
presenter.test {
val initialState = awaitItem()
assertThat(initialState).isEqualTo(PinnedMessagesBannerState.Hidden)
}
}
@Test @Test
fun `present - loading state`() = runTest { fun `present - loading state`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
@ -188,14 +177,10 @@ class PinnedMessagesBannerPresenterTest {
) )
), ),
syncService: SyncService = FakeSyncService(), syncService: SyncService = FakeSyncService(),
isFeatureEnabled: Boolean = true,
): PinnedMessagesBannerPresenter { ): PinnedMessagesBannerPresenter {
val timelineProvider = PinnedEventsTimelineProvider( val timelineProvider = PinnedEventsTimelineProvider(
room = room, room = room,
syncService = syncService, syncService = syncService,
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
),
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
) )
timelineProvider.launchIn(backgroundScope) timelineProvider.launchIn(backgroundScope)

View file

@ -17,8 +17,6 @@ import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProv
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
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.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.sync.SyncService import io.element.android.libraries.matrix.api.sync.SyncService
@ -51,34 +49,17 @@ import org.junit.Test
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
class PinnedMessagesListPresenterTest { class PinnedMessagesListPresenterTest {
@Test @Test
fun `present - initial state feature disabled`() = runTest { fun `present - initial state`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}
) )
) val presenter = createPinnedMessagesListPresenter(room = room)
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = false)
presenter.test {
val initialState = awaitItem()
assertThat(initialState).isEqualTo(PinnedMessagesListState.Loading)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - initial state feature enabled`() = runTest {
val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) },
).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}
)
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true)
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState).isEqualTo(PinnedMessagesListState.Loading) assertThat(initialState).isEqualTo(PinnedMessagesListState.Loading)
@ -90,15 +71,15 @@ class PinnedMessagesListPresenterTest {
fun `present - timeline failure state`() = runTest { fun `present - timeline failure state`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.failure(RuntimeException()) }, createTimelineResult = { Result.failure(RuntimeException()) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val failureState = awaitItem() val failureState = awaitItem()
@ -111,15 +92,15 @@ class PinnedMessagesListPresenterTest {
fun `present - empty state`() = runTest { fun `present - empty state`() = runTest {
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf())) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf()))
}, },
createTimelineResult = { Result.success(FakeTimeline()) }, createTimelineResult = { Result.success(FakeTimeline()) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val emptyState = awaitItem() val emptyState = awaitItem()
@ -133,15 +114,15 @@ class PinnedMessagesListPresenterTest {
val pinnedEventsTimeline = createPinnedMessagesTimeline() val pinnedEventsTimeline = createPinnedMessagesTimeline()
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.success(pinnedEventsTimeline) }, createTimelineResult = { Result.success(pinnedEventsTimeline) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -162,15 +143,15 @@ class PinnedMessagesListPresenterTest {
val analyticsService = FakeAnalyticsService() val analyticsService = FakeAnalyticsService()
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.success(pinnedEventsTimeline) }, createTimelineResult = { Result.success(pinnedEventsTimeline) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true, analyticsService = analyticsService) val presenter = createPinnedMessagesListPresenter(room = room, analyticsService = analyticsService)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -210,15 +191,15 @@ class PinnedMessagesListPresenterTest {
val pinnedEventsTimeline = createPinnedMessagesTimeline() val pinnedEventsTimeline = createPinnedMessagesTimeline()
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.success(pinnedEventsTimeline) }, createTimelineResult = { Result.success(pinnedEventsTimeline) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -241,15 +222,15 @@ class PinnedMessagesListPresenterTest {
val pinnedEventsTimeline = createPinnedMessagesTimeline() val pinnedEventsTimeline = createPinnedMessagesTimeline()
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.success(pinnedEventsTimeline) }, createTimelineResult = { Result.success(pinnedEventsTimeline) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -272,15 +253,15 @@ class PinnedMessagesListPresenterTest {
val pinnedEventsTimeline = createPinnedMessagesTimeline() val pinnedEventsTimeline = createPinnedMessagesTimeline()
val room = FakeJoinedRoom( val room = FakeJoinedRoom(
baseRoom = FakeBaseRoom( baseRoom = FakeBaseRoom(
canRedactOwnResult = { Result.success(true) }, canRedactOwnResult = { Result.success(true) },
canRedactOtherResult = { Result.success(true) }, canRedactOtherResult = { Result.success(true) },
canUserPinUnpinResult = { Result.success(true) }, canUserPinUnpinResult = { Result.success(true) },
).apply { ).apply {
givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID)))
}, },
createTimelineResult = { Result.success(pinnedEventsTimeline) }, createTimelineResult = { Result.success(pinnedEventsTimeline) },
) )
val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator, isFeatureEnabled = true) val presenter = createPinnedMessagesListPresenter(room = room, navigator = navigator)
presenter.test { presenter.test {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
@ -315,15 +296,11 @@ class PinnedMessagesListPresenterTest {
navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(), navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(),
room: JoinedRoom = FakeJoinedRoom(), room: JoinedRoom = FakeJoinedRoom(),
syncService: SyncService = FakeSyncService(), syncService: SyncService = FakeSyncService(),
isFeatureEnabled: Boolean = true,
analyticsService: AnalyticsService = FakeAnalyticsService(), analyticsService: AnalyticsService = FakeAnalyticsService(),
): PinnedMessagesListPresenter { ): PinnedMessagesListPresenter {
val timelineProvider = PinnedEventsTimelineProvider( val timelineProvider = PinnedEventsTimelineProvider(
room = room, room = room,
syncService = syncService, syncService = syncService,
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled)
),
dispatchers = testCoroutineDispatchers(), dispatchers = testCoroutineDispatchers(),
) )
timelineProvider.launchIn(backgroundScope) timelineProvider.launchIn(backgroundScope)

View file

@ -32,9 +32,6 @@ import io.element.android.features.messages.impl.utils.FakeTextPillificationHelp
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter import io.element.android.libraries.androidutils.filesize.FakeFileSizeFormatter
import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.api.media.AudioDetails import io.element.android.libraries.matrix.api.media.AudioDetails
import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo import io.element.android.libraries.matrix.api.media.FileInfo
@ -430,35 +427,6 @@ class TimelineItemContentMessageFactoryTest {
assertThat(result).isEqualTo(expected) assertThat(result).isEqualTo(expected)
} }
@Test
fun `test create VoiceMessageType feature disabled`() = runTest {
val sut = createTimelineItemContentMessageFactory(
featureFlagService = FakeFeatureFlagService(
initialState = mapOf(
FeatureFlags.VoiceMessages.key to false,
)
)
)
val result = sut.create(
content = createMessageContent(type = VoiceMessageType("filename", null, null, MediaSource("url"), null, null)),
senderDisambiguatedDisplayName = "Bob",
eventId = AN_EVENT_ID,
)
val expected = TimelineItemAudioContent(
filename = "filename",
fileSize = 0L,
caption = null,
formattedCaption = null,
isEdited = false,
duration = Duration.ZERO,
mediaSource = MediaSource(url = "url", json = null),
mimeType = MimeTypes.OctetStream,
formattedFileSize = "0 Bytes",
fileExtension = ""
)
assertThat(result).isEqualTo(expected)
}
@Test @Test
fun `test create ImageMessageType`() = runTest { fun `test create ImageMessageType`() = runTest {
val sut = createTimelineItemContentMessageFactory() val sut = createTimelineItemContentMessageFactory()
@ -794,13 +762,11 @@ class TimelineItemContentMessageFactoryTest {
} }
private fun createTimelineItemContentMessageFactory( private fun createTimelineItemContentMessageFactory(
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
htmlConverterTransform: (String) -> CharSequence = { it }, htmlConverterTransform: (String) -> CharSequence = { it },
permalinkParser: FakePermalinkParser = FakePermalinkParser(), permalinkParser: FakePermalinkParser = FakePermalinkParser(),
) = TimelineItemContentMessageFactory( ) = TimelineItemContentMessageFactory(
fileSizeFormatter = FakeFileSizeFormatter(), fileSizeFormatter = FakeFileSizeFormatter(),
fileExtensionExtractor = FileExtensionExtractorWithoutValidation(), fileExtensionExtractor = FileExtensionExtractorWithoutValidation(),
featureFlagService = featureFlagService,
htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform), htmlConverterProvider = FakeHtmlConverterProvider(htmlConverterTransform),
permalinkParser = permalinkParser, permalinkParser = permalinkParser,
textPillificationHelper = FakeTextPillificationHelper(), textPillificationHelper = FakeTextPillificationHelper(),

View file

@ -23,8 +23,6 @@ import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.indicator.api.IndicatorService
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
@ -42,7 +40,6 @@ class PreferencesRootPresenter @Inject constructor(
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val versionFormatter: VersionFormatter, private val versionFormatter: VersionFormatter,
private val snackbarDispatcher: SnackbarDispatcher, private val snackbarDispatcher: SnackbarDispatcher,
private val featureFlagService: FeatureFlagService,
private val indicatorService: IndicatorService, private val indicatorService: IndicatorService,
private val directLogoutPresenter: Presenter<DirectLogoutState>, private val directLogoutPresenter: Presenter<DirectLogoutState>,
private val showDeveloperSettingsProvider: ShowDeveloperSettingsProvider, private val showDeveloperSettingsProvider: ShowDeveloperSettingsProvider,
@ -60,15 +57,6 @@ class PreferencesRootPresenter @Inject constructor(
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()
val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() } val hasAnalyticsProviders = remember { analyticsService.getAvailableAnalyticsProviders().isNotEmpty() }
val showNotificationSettings = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
showNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings)
}
val showLockScreenSettings = remember { mutableStateOf(false) }
LaunchedEffect(Unit) {
showLockScreenSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.PinUnlock)
}
// We should display the 'complete verification' option if the current session can be verified // We should display the 'complete verification' option if the current session can be verified
val canVerifyUserSession by sessionVerificationService.needsSessionVerification.collectAsState(false) val canVerifyUserSession by sessionVerificationService.needsSessionVerification.collectAsState(false)
@ -122,8 +110,6 @@ class PreferencesRootPresenter @Inject constructor(
canReportBug = canReportBug, canReportBug = canReportBug,
showDeveloperSettings = showDeveloperSettings, showDeveloperSettings = showDeveloperSettings,
canDeactivateAccount = canDeactivateAccount, canDeactivateAccount = canDeactivateAccount,
showNotificationSettings = showNotificationSettings.value,
showLockScreenSettings = showLockScreenSettings.value,
showBlockedUsersItem = showBlockedUsersItem, showBlockedUsersItem = showBlockedUsersItem,
directLogoutState = directLogoutState, directLogoutState = directLogoutState,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,

View file

@ -24,8 +24,6 @@ data class PreferencesRootState(
val showAnalyticsSettings: Boolean, val showAnalyticsSettings: Boolean,
val showDeveloperSettings: Boolean, val showDeveloperSettings: Boolean,
val canDeactivateAccount: Boolean, val canDeactivateAccount: Boolean,
val showLockScreenSettings: Boolean,
val showNotificationSettings: Boolean,
val showBlockedUsersItem: Boolean, val showBlockedUsersItem: Boolean,
val directLogoutState: DirectLogoutState, val directLogoutState: DirectLogoutState,
val snackbarMessage: SnackbarMessage?, val snackbarMessage: SnackbarMessage?,

View file

@ -27,8 +27,6 @@ fun aPreferencesRootState(
showAnalyticsSettings = true, showAnalyticsSettings = true,
canReportBug = true, canReportBug = true,
showDeveloperSettings = true, showDeveloperSettings = true,
showNotificationSettings = true,
showLockScreenSettings = true,
showBlockedUsersItem = true, showBlockedUsersItem = true,
canDeactivateAccount = true, canDeactivateAccount = true,
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete), snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),

View file

@ -121,20 +121,16 @@ private fun ColumnScope.ManageAppSection(
onOpenLockScreenSettings: () -> Unit, onOpenLockScreenSettings: () -> Unit,
onSecureBackupClick: () -> Unit, onSecureBackupClick: () -> Unit,
) { ) {
if (state.showNotificationSettings) { ListItem(
ListItem( headlineContent = { Text(stringResource(id = R.string.screen_notification_settings_title)) },
headlineContent = { Text(stringResource(id = R.string.screen_notification_settings_title)) }, leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())),
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Notifications())), onClick = onOpenNotificationSettings,
onClick = onOpenNotificationSettings, )
) ListItem(
} headlineContent = { Text(stringResource(id = CommonStrings.common_screen_lock)) },
if (state.showLockScreenSettings) { leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
ListItem( onClick = onOpenLockScreenSettings,
headlineContent = { Text(stringResource(id = CommonStrings.common_screen_lock)) }, )
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Lock())),
onClick = onOpenLockScreenSettings,
)
}
if (state.showSecureBackup) { if (state.showSecureBackup) {
ListItem( ListItem(
headlineContent = { Text(stringResource(id = CommonStrings.common_encryption)) }, headlineContent = { Text(stringResource(id = CommonStrings.common_encryption)) },
@ -143,9 +139,7 @@ private fun ColumnScope.ManageAppSection(
onClick = onSecureBackupClick, onClick = onSecureBackupClick,
) )
} }
if (state.showNotificationSettings || state.showLockScreenSettings || state.showSecureBackup) { HorizontalDivider()
HorizontalDivider()
}
} }
@Composable @Composable

View file

@ -16,7 +16,6 @@ import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsP
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.core.meta.BuildType
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.indicator.api.IndicatorService import io.element.android.libraries.indicator.api.IndicatorService
import io.element.android.libraries.indicator.test.FakeIndicatorService import io.element.android.libraries.indicator.test.FakeIndicatorService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
@ -77,8 +76,6 @@ class PreferencesRootPresenterTest {
assertThat(loadedState.devicesManagementUrl).isNull() assertThat(loadedState.devicesManagementUrl).isNull()
assertThat(loadedState.showAnalyticsSettings).isFalse() assertThat(loadedState.showAnalyticsSettings).isFalse()
assertThat(loadedState.showDeveloperSettings).isTrue() assertThat(loadedState.showDeveloperSettings).isTrue()
assertThat(loadedState.showLockScreenSettings).isTrue()
assertThat(loadedState.showNotificationSettings).isTrue()
assertThat(loadedState.canDeactivateAccount).isTrue() assertThat(loadedState.canDeactivateAccount).isTrue()
assertThat(loadedState.canReportBug).isTrue() assertThat(loadedState.canReportBug).isTrue()
assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState()) assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState())
@ -194,7 +191,6 @@ class PreferencesRootPresenterTest {
analyticsService = FakeAnalyticsService(), analyticsService = FakeAnalyticsService(),
versionFormatter = FakeVersionFormatter(), versionFormatter = FakeVersionFormatter(),
snackbarDispatcher = SnackbarDispatcher(), snackbarDispatcher = SnackbarDispatcher(),
featureFlagService = FakeFeatureFlagService(),
indicatorService = indicatorService, indicatorService = indicatorService,
directLogoutPresenter = { aDirectLogoutState() }, directLogoutPresenter = { aDirectLogoutState() },
showDeveloperSettingsProvider = showDeveloperSettingsProvider, showDeveloperSettingsProvider = showDeveloperSettingsProvider,

View file

@ -12,14 +12,12 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomEvent
import io.element.android.features.leaveroom.api.LeaveRoomState import io.element.android.features.leaveroom.api.LeaveRoomState
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
import io.element.android.features.roomcall.api.RoomCallState import io.element.android.features.roomcall.api.RoomCallState
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState import io.element.android.features.roomdetails.impl.securityandprivacy.permissions.securityAndPrivacyPermissionsAsState
@ -69,7 +67,6 @@ class RoomDetailsPresenter @Inject constructor(
private val roomCallStatePresenter: Presenter<RoomCallState>, private val roomCallStatePresenter: Presenter<RoomCallState>,
private val dispatchers: CoroutineDispatchers, private val dispatchers: CoroutineDispatchers,
private val analyticsService: AnalyticsService, private val analyticsService: AnalyticsService,
private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled,
private val clipboardHelper: ClipboardHelper, private val clipboardHelper: ClipboardHelper,
private val appPreferencesStore: AppPreferencesStore, private val appPreferencesStore: AppPreferencesStore,
) : Presenter<RoomDetailsState> { ) : Presenter<RoomDetailsState> {
@ -77,7 +74,6 @@ class RoomDetailsPresenter @Inject constructor(
override fun present(): RoomDetailsState { override fun present(): RoomDetailsState {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val leaveRoomState = leaveRoomPresenter.present() val leaveRoomState = leaveRoomPresenter.present()
val canShowNotificationSettings = remember { mutableStateOf(false) }
val roomInfo by room.roomInfoFlow.collectAsState() val roomInfo by room.roomInfoFlow.collectAsState()
val isUserAdmin = room.isOwnUserAdmin() val isUserAdmin = room.isOwnUserAdmin()
val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
@ -88,19 +84,11 @@ class RoomDetailsPresenter @Inject constructor(
val isFavorite by remember { derivedStateOf { roomInfo.isFavorite } } val isFavorite by remember { derivedStateOf { roomInfo.isFavorite } }
val joinRule by remember { derivedStateOf { roomInfo.joinRule } } val joinRule by remember { derivedStateOf { roomInfo.joinRule } }
val canShowPinnedMessages = isPinnedMessagesFeatureEnabled()
val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } } val pinnedMessagesCount by remember { derivedStateOf { roomInfo.pinnedEventIds.size } }
val canShowMediaGallery by remember {
featureFlagService.isFeatureEnabledFlow(FeatureFlags.MediaGallery)
}.collectAsState(false)
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
canShowNotificationSettings.value = featureFlagService.isFeatureEnabled(FeatureFlags.NotificationSettings) room.updateRoomNotificationSettings()
if (canShowNotificationSettings.value) { observeNotificationSettings()
room.updateRoomNotificationSettings()
observeNotificationSettings()
}
} }
val membersState by room.membersStateFlow.collectAsState() val membersState by room.membersStateFlow.collectAsState()
@ -197,7 +185,6 @@ class RoomDetailsPresenter @Inject constructor(
isEncrypted = isEncrypted, isEncrypted = isEncrypted,
canInvite = canInvite, canInvite = canInvite,
canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room, canEdit = (canEditAvatar || canEditName || canEditTopic) && roomType == RoomDetailsType.Room,
canShowNotificationSettings = canShowNotificationSettings.value,
roomCallState = roomCallState, roomCallState = roomCallState,
roomType = roomType, roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState, roomMemberDetailsState = roomMemberDetailsState,
@ -207,8 +194,6 @@ class RoomDetailsPresenter @Inject constructor(
displayRolesAndPermissionsSettings = !isDm && isUserAdmin, displayRolesAndPermissionsSettings = !isDm && isUserAdmin,
isPublic = joinRule == JoinRule.Public, isPublic = joinRule == JoinRule.Public,
heroes = roomInfo.heroes.toPersistentList(), heroes = roomInfo.heroes.toPersistentList(),
canShowPinnedMessages = canShowPinnedMessages,
canShowMediaGallery = canShowMediaGallery,
pinnedMessagesCount = pinnedMessagesCount, pinnedMessagesCount = pinnedMessagesCount,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
canShowKnockRequests = canShowKnockRequests, canShowKnockRequests = canShowKnockRequests,

View file

@ -32,7 +32,6 @@ data class RoomDetailsState(
val roomMemberDetailsState: UserProfileState?, val roomMemberDetailsState: UserProfileState?,
val canEdit: Boolean, val canEdit: Boolean,
val canInvite: Boolean, val canInvite: Boolean,
val canShowNotificationSettings: Boolean,
val roomCallState: RoomCallState, val roomCallState: RoomCallState,
val leaveRoomState: LeaveRoomState, val leaveRoomState: LeaveRoomState,
val roomNotificationSettings: RoomNotificationSettings?, val roomNotificationSettings: RoomNotificationSettings?,
@ -40,8 +39,6 @@ data class RoomDetailsState(
val displayRolesAndPermissionsSettings: Boolean, val displayRolesAndPermissionsSettings: Boolean,
val isPublic: Boolean, val isPublic: Boolean,
val heroes: ImmutableList<MatrixUser>, val heroes: ImmutableList<MatrixUser>,
val canShowPinnedMessages: Boolean,
val canShowMediaGallery: Boolean,
val pinnedMessagesCount: Int?, val pinnedMessagesCount: Int?,
val snackbarMessage: SnackbarMessage?, val snackbarMessage: SnackbarMessage?,
val canShowKnockRequests: Boolean, val canShowKnockRequests: Boolean,

View file

@ -100,7 +100,6 @@ fun aRoomDetailsState(
isEncrypted: Boolean = true, isEncrypted: Boolean = true,
canInvite: Boolean = false, canInvite: Boolean = false,
canEdit: Boolean = false, canEdit: Boolean = false,
canShowNotificationSettings: Boolean = true,
roomCallState: RoomCallState = aStandByCallState(), roomCallState: RoomCallState = aStandByCallState(),
roomType: RoomDetailsType = RoomDetailsType.Room, roomType: RoomDetailsType = RoomDetailsType.Room,
roomMemberDetailsState: UserProfileState? = null, roomMemberDetailsState: UserProfileState? = null,
@ -110,8 +109,6 @@ fun aRoomDetailsState(
displayAdminSettings: Boolean = false, displayAdminSettings: Boolean = false,
isPublic: Boolean = true, isPublic: Boolean = true,
heroes: List<MatrixUser> = emptyList(), heroes: List<MatrixUser> = emptyList(),
canShowPinnedMessages: Boolean = true,
canShowMediaGallery: Boolean = true,
pinnedMessagesCount: Int? = null, pinnedMessagesCount: Int? = null,
snackbarMessage: SnackbarMessage? = null, snackbarMessage: SnackbarMessage? = null,
canShowKnockRequests: Boolean = false, canShowKnockRequests: Boolean = false,
@ -132,7 +129,6 @@ fun aRoomDetailsState(
isEncrypted = isEncrypted, isEncrypted = isEncrypted,
canInvite = canInvite, canInvite = canInvite,
canEdit = canEdit, canEdit = canEdit,
canShowNotificationSettings = canShowNotificationSettings,
roomCallState = roomCallState, roomCallState = roomCallState,
roomType = roomType, roomType = roomType,
roomMemberDetailsState = roomMemberDetailsState, roomMemberDetailsState = roomMemberDetailsState,
@ -142,8 +138,6 @@ fun aRoomDetailsState(
displayRolesAndPermissionsSettings = displayAdminSettings, displayRolesAndPermissionsSettings = displayAdminSettings,
isPublic = isPublic, isPublic = isPublic,
heroes = heroes.toPersistentList(), heroes = heroes.toPersistentList(),
canShowPinnedMessages = canShowPinnedMessages,
canShowMediaGallery = canShowMediaGallery,
pinnedMessagesCount = pinnedMessagesCount, pinnedMessagesCount = pinnedMessagesCount,
snackbarMessage = snackbarMessage, snackbarMessage = snackbarMessage,
canShowKnockRequests = canShowKnockRequests, canShowKnockRequests = canShowKnockRequests,

View file

@ -185,7 +185,7 @@ fun RoomDetailsView(
} }
PreferenceCategory { PreferenceCategory {
if (state.canShowNotificationSettings && state.roomNotificationSettings != null) { if (state.roomNotificationSettings != null) {
NotificationItem( NotificationItem(
isDefaultMode = state.roomNotificationSettings.isDefault, isDefaultMode = state.roomNotificationSettings.isDefault,
openRoomNotificationSettings = openRoomNotificationSettings openRoomNotificationSettings = openRoomNotificationSettings
@ -237,20 +237,16 @@ fun RoomDetailsView(
} }
PreferenceCategory { PreferenceCategory {
if (state.canShowPinnedMessages) { PinnedMessagesItem(
PinnedMessagesItem( pinnedMessagesCount = state.pinnedMessagesCount,
pinnedMessagesCount = state.pinnedMessagesCount, onPinnedMessagesClick = onPinnedMessagesClick
onPinnedMessagesClick = onPinnedMessagesClick )
)
}
PollsItem( PollsItem(
openPollHistory = openPollHistory openPollHistory = openPollHistory
) )
if (state.canShowMediaGallery) { MediaGalleryItem(
MediaGalleryItem( onClick = openMediaGallery
onClick = openMediaGallery )
)
}
} }
if (state.roomType is RoomDetailsType.Dm && state.roomMemberDetailsState != null) { if (state.roomType is RoomDetailsType.Dm && state.roomMemberDetailsState != null) {
@ -337,8 +333,7 @@ private fun MainActionsSection(
.padding(horizontal = 16.dp), .padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
) { ) {
val roomNotificationSettings = state.roomNotificationSettings state.roomNotificationSettings?.let { roomNotificationSettings ->
if (state.canShowNotificationSettings && roomNotificationSettings != null) {
if (roomNotificationSettings.mode == RoomNotificationMode.MUTE) { if (roomNotificationSettings.mode == RoomNotificationMode.MUTE) {
MainActionButton( MainActionButton(
title = stringResource(CommonStrings.common_unmute), title = stringResource(CommonStrings.common_unmute),

View file

@ -79,11 +79,9 @@ class RoomDetailsPresenterTest {
analyticsService: AnalyticsService = FakeAnalyticsService(), analyticsService: AnalyticsService = FakeAnalyticsService(),
featureFlagService: FeatureFlagService = FakeFeatureFlagService( featureFlagService: FeatureFlagService = FakeFeatureFlagService(
mapOf( mapOf(
FeatureFlags.NotificationSettings.key to true,
FeatureFlags.Knock.key to false, FeatureFlags.Knock.key to false,
) )
), ),
isPinnedMessagesFeatureEnabled: Boolean = true,
encryptionService: FakeEncryptionService = FakeEncryptionService(), encryptionService: FakeEncryptionService = FakeEncryptionService(),
clipboardHelper: ClipboardHelper = FakeClipboardHelper(), clipboardHelper: ClipboardHelper = FakeClipboardHelper(),
appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore() appPreferencesStore: AppPreferencesStore = InMemoryAppPreferencesStore()
@ -111,7 +109,6 @@ class RoomDetailsPresenterTest {
leaveRoomPresenter = { leaveRoomState }, leaveRoomPresenter = { leaveRoomState },
roomCallStatePresenter = { aStandByCallState() }, roomCallStatePresenter = { aStandByCallState() },
dispatchers = dispatchers, dispatchers = dispatchers,
isPinnedMessagesFeatureEnabled = { isPinnedMessagesFeatureEnabled },
analyticsService = analyticsService, analyticsService = analyticsService,
clipboardHelper = clipboardHelper, clipboardHelper = clipboardHelper,
appPreferencesStore = appPreferencesStore, appPreferencesStore = appPreferencesStore,
@ -133,7 +130,6 @@ class RoomDetailsPresenterTest {
assertThat(initialState.roomAvatarUrl).isEqualTo(room.info().avatarUrl) assertThat(initialState.roomAvatarUrl).isEqualTo(room.info().avatarUrl)
assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.info().topic!!)) assertThat(initialState.roomTopic).isEqualTo(RoomTopicState.ExistingTopic(room.info().topic!!))
assertThat(initialState.memberCount).isEqualTo(room.info().joinedMembersCount) assertThat(initialState.memberCount).isEqualTo(room.info().joinedMembersCount)
assertThat(initialState.canShowPinnedMessages).isTrue()
assertThat(initialState.pinnedMessagesCount).isEqualTo(0) assertThat(initialState.pinnedMessagesCount).isEqualTo(0)
assertThat(initialState.canShowSecurityAndPrivacy).isFalse() assertThat(initialState.canShowSecurityAndPrivacy).isFalse()
assertThat(initialState.showDebugInfo).isFalse() assertThat(initialState.showDebugInfo).isFalse()

View file

@ -1,14 +0,0 @@
/*
* Copyright 2024 New Vector 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.share.api
import kotlinx.coroutines.CoroutineScope
interface ShareService {
fun observeFeatureFlag(coroutineScope: CoroutineScope)
}

View file

@ -1,74 +0,0 @@
/*
* Copyright 2024 New Vector 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.share.impl
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.features.share.api.ShareService
import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import timber.log.Timber
import javax.inject.Inject
@ContributesBinding(AppScope::class)
class DefaultShareService @Inject constructor(
private val featureFlagService: FeatureFlagService,
@ApplicationContext private val context: Context,
) : ShareService {
override fun observeFeatureFlag(coroutineScope: CoroutineScope) {
val shareActivityComponent = getShareActivityComponent()
?: return Unit.also {
Timber.w("ShareActivity not found")
}
featureFlagService.isFeatureEnabledFlow(FeatureFlags.IncomingShare)
.onEach { enabled ->
shareActivityComponent.enableOrDisable(enabled)
}
.launchIn(coroutineScope)
}
private fun getShareActivityComponent(): ComponentName? {
return context.packageManager
.getPackageInfo(
context.packageName,
PackageManager.GET_ACTIVITIES or PackageManager.MATCH_DISABLED_COMPONENTS
)
.activities
?.firstOrNull { it.name.endsWith(".ShareActivity") }
?.let { shareActivityInfo ->
ComponentName(
shareActivityInfo.packageName,
shareActivityInfo.name,
)
}
}
private fun ComponentName.enableOrDisable(enabled: Boolean) {
val state = if (enabled) {
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
} else {
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
}
try {
context.packageManager.setComponentEnabledSetting(
this,
state,
PackageManager.DONT_KILL_APP,
)
} catch (e: Exception) {
Timber.e(e, "Failed to enable or disable the component")
}
}
}

View file

@ -1,19 +0,0 @@
/*
* Copyright 2024 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.features.share.test"
}
dependencies {
implementation(projects.features.share.api)
implementation(libs.coroutines.core)
implementation(projects.tests.testutils)
}

View file

@ -1,20 +0,0 @@
/*
* Copyright 2024 New Vector 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.share.test
import io.element.android.features.share.api.ShareService
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.coroutines.CoroutineScope
class FakeShareService(
private val observeFeatureFlagLambda: (CoroutineScope) -> Unit = { lambdaError() }
) : ShareService {
override fun observeFeatureFlag(coroutineScope: CoroutineScope) {
observeFeatureFlagLambda(coroutineScope)
}
}

View file

@ -7,7 +7,6 @@
package io.element.android.libraries.featureflag.api package io.element.android.libraries.featureflag.api
import io.element.android.appconfig.OnBoardingConfig
import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.core.meta.BuildType
@ -21,46 +20,6 @@ enum class FeatureFlags(
override val defaultValue: (BuildMeta) -> Boolean, override val defaultValue: (BuildMeta) -> Boolean,
override val isFinished: Boolean, override val isFinished: Boolean,
) : Feature { ) : Feature {
LocationSharing(
key = "feature.locationsharing",
title = "Allow user to share location",
defaultValue = { true },
isFinished = true,
),
Polls(
key = "feature.polls",
title = "Polls",
description = "Create poll and render poll events in the timeline",
defaultValue = { true },
isFinished = true,
),
NotificationSettings(
key = "feature.notificationsettings",
title = "Show notification settings",
defaultValue = { true },
isFinished = true,
),
VoiceMessages(
key = "feature.voicemessages",
title = "Voice messages",
description = "Send and receive voice messages",
defaultValue = { true },
isFinished = true,
),
PinUnlock(
key = "feature.pinunlock",
title = "Pin unlock",
description = "Allow user to lock/unlock the app with a pin code or biometrics",
defaultValue = { true },
isFinished = true,
),
MarkAsUnread(
key = "feature.markAsUnread",
title = "Mark as unread",
description = "Allow user to mark a room as unread",
defaultValue = { true },
isFinished = false,
),
RoomDirectorySearch( RoomDirectorySearch(
key = "feature.roomdirectorysearch", key = "feature.roomdirectorysearch",
title = "Room directory search", title = "Room directory search",
@ -75,27 +34,6 @@ enum class FeatureFlags(
defaultValue = { false }, defaultValue = { false },
isFinished = false, isFinished = false,
), ),
QrCodeLogin(
key = "feature.qrCodeLogin",
title = "Enable login using QR code",
description = "Allow the user to login using the QR code flow",
defaultValue = { OnBoardingConfig.CAN_LOGIN_WITH_QR_CODE },
isFinished = false,
),
IncomingShare(
key = "feature.incomingShare",
title = "Incoming Share support",
description = "Allow the application to receive data from other applications",
defaultValue = { true },
isFinished = false,
),
PinnedEvents(
key = "feature.pinnedEvents",
title = "Pinned Events",
description = "Allow user to pin events in a room",
defaultValue = { true },
isFinished = false,
),
SyncOnPush( SyncOnPush(
key = "feature.syncOnPush", key = "feature.syncOnPush",
title = "Sync on push", title = "Sync on push",
@ -137,34 +75,6 @@ enum class FeatureFlags(
defaultValue = { false }, defaultValue = { false },
isFinished = false, isFinished = false,
), ),
MediaUploadOnSendQueue(
key = "feature.media_upload_through_send_queue",
title = "Media upload through send queue",
description = "Support for treating media uploads as regular events, with an improved retry and cancellation implementation.",
defaultValue = { true },
isFinished = true,
),
MediaCaptionCreation(
key = "feature.media_caption_creation",
title = "Allow creation of media captions",
description = null,
defaultValue = { true },
isFinished = false,
),
MediaCaptionWarning(
key = "feature.media_caption_creation_warning",
title = "Show a compatibility warning on media captions creation",
description = null,
defaultValue = { true },
isFinished = false,
),
MediaGallery(
key = "feature.media_gallery",
title = "Allow user to open the media gallery",
description = null,
defaultValue = { true },
isFinished = false,
),
PrintLogsToLogcat( PrintLogsToLogcat(
key = "feature.print_logs_to_logcat", key = "feature.print_logs_to_logcat",
title = "Print logs to logcat", title = "Print logs to logcat",
@ -175,15 +85,6 @@ enum class FeatureFlags(
// False so it's displayed in the developer options screen // False so it's displayed in the developer options screen
isFinished = false, isFinished = false,
), ),
SharePos(
key = "feature.share_pos_v2",
title = "Share pos in sliding sync",
description = "Keep the sliding sync pos to make initial syncs faster. Requires an app restart to take effect." +
"\n\nWARNING: this may cause issues with syncs.",
defaultValue = { true },
// False so it's displayed in the developer options screen
isFinished = false,
),
SelectableMediaQuality( SelectableMediaQuality(
key = "feature.selectable_media_quality", key = "feature.selectable_media_quality",
title = "Select media quality per upload", title = "Select media quality per upload",

View file

@ -19,8 +19,8 @@ class DefaultFeatureFlagServiceTest {
fun `given service without provider when feature is checked then it returns the default value`() = runTest { fun `given service without provider when feature is checked then it returns the default value`() = runTest {
val buildMeta = aBuildMeta() val buildMeta = aBuildMeta()
val featureFlagService = DefaultFeatureFlagService(emptySet(), buildMeta) val featureFlagService = DefaultFeatureFlagService(emptySet(), buildMeta)
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test {
assertThat(awaitItem()).isEqualTo(FeatureFlags.LocationSharing.defaultValue(buildMeta)) assertThat(awaitItem()).isEqualTo(FeatureFlags.Space.defaultValue(buildMeta))
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
} }
@ -28,7 +28,7 @@ class DefaultFeatureFlagServiceTest {
@Test @Test
fun `given service without provider when set enabled feature is called then it returns false`() = runTest { fun `given service without provider when set enabled feature is called then it returns false`() = runTest {
val featureFlagService = DefaultFeatureFlagService(emptySet(), aBuildMeta()) val featureFlagService = DefaultFeatureFlagService(emptySet(), aBuildMeta())
val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) val result = featureFlagService.setFeatureEnabled(FeatureFlags.Space, true)
assertThat(result).isFalse() assertThat(result).isFalse()
} }
@ -37,7 +37,7 @@ class DefaultFeatureFlagServiceTest {
val buildMeta = aBuildMeta() val buildMeta = aBuildMeta()
val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta)
val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta)
val result = featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) val result = featureFlagService.setFeatureEnabled(FeatureFlags.Space, true)
assertThat(result).isTrue() assertThat(result).isTrue()
} }
@ -46,10 +46,10 @@ class DefaultFeatureFlagServiceTest {
val buildMeta = aBuildMeta() val buildMeta = aBuildMeta()
val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta) val featureFlagProvider = FakeMutableFeatureFlagProvider(0, buildMeta)
val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta) val featureFlagService = DefaultFeatureFlagService(setOf(featureFlagProvider), buildMeta)
featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, true) featureFlagService.setFeatureEnabled(FeatureFlags.Space, true)
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test {
assertThat(awaitItem()).isTrue() assertThat(awaitItem()).isTrue()
featureFlagService.setFeatureEnabled(FeatureFlags.LocationSharing, false) featureFlagService.setFeatureEnabled(FeatureFlags.Space, false)
assertThat(awaitItem()).isFalse() assertThat(awaitItem()).isFalse()
} }
} }
@ -60,9 +60,9 @@ class DefaultFeatureFlagServiceTest {
val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY, buildMeta) val lowPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(LOW_PRIORITY, buildMeta)
val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY, buildMeta) val highPriorityFeatureFlagProvider = FakeMutableFeatureFlagProvider(HIGH_PRIORITY, buildMeta)
val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider), buildMeta) val featureFlagService = DefaultFeatureFlagService(setOf(lowPriorityFeatureFlagProvider, highPriorityFeatureFlagProvider), buildMeta)
lowPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, false) lowPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.Space, false)
highPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.LocationSharing, true) highPriorityFeatureFlagProvider.setFeatureEnabled(FeatureFlags.Space, true)
featureFlagService.isFeatureEnabledFlow(FeatureFlags.LocationSharing).test { featureFlagService.isFeatureEnabledFlow(FeatureFlags.Space).test {
assertThat(awaitItem()).isTrue() assertThat(awaitItem()).isTrue()
} }
} }

View file

@ -14,7 +14,6 @@ import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
@ -136,7 +135,6 @@ class RustMatrixClient(
baseCacheDirectory: File, baseCacheDirectory: File,
clock: SystemClock, clock: SystemClock,
timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
featureFlagService: FeatureFlagService,
) : MatrixClient { ) : MatrixClient {
override val sessionId: UserId = UserId(innerClient.userId()) override val sessionId: UserId = UserId(innerClient.userId())
override val deviceId: DeviceId = DeviceId(innerClient.deviceId()) override val deviceId: DeviceId = DeviceId(innerClient.deviceId())
@ -205,7 +203,6 @@ class RustMatrixClient(
roomContentForwarder = RoomContentForwarder(innerRoomListService), roomContentForwarder = RoomContentForwarder(innerRoomListService),
roomSyncSubscriber = roomSyncSubscriber, roomSyncSubscriber = roomSyncSubscriber,
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory, timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
featureFlagService = featureFlagService,
roomMembershipObserver = roomMembershipObserver, roomMembershipObserver = roomMembershipObserver,
roomInfoMapper = roomInfoMapper, roomInfoMapper = roomInfoMapper,
) )

View file

@ -78,7 +78,7 @@ class RustMatrixClientFactory @Inject constructor(
client.setUtdDelegate(UtdTracker(analyticsService)) client.setUtdDelegate(UtdTracker(analyticsService))
val syncService = client.syncService() val syncService = client.syncService()
.withSharePos(enable = featureFlagService.isFeatureEnabled(FeatureFlags.SharePos)) .withSharePos(true)
.withOfflineMode() .withOfflineMode()
.finish() .finish()
@ -93,7 +93,6 @@ class RustMatrixClientFactory @Inject constructor(
baseCacheDirectory = cacheDirectory, baseCacheDirectory = cacheDirectory,
clock = clock, clock = clock,
timelineEventTypeFilterFactory = timelineEventTypeFilterFactory, timelineEventTypeFilterFactory = timelineEventTypeFilterFactory,
featureFlagService = featureFlagService,
).also { ).also {
Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'") Timber.tag(it.toString()).d("Creating Client with access token '$anonymizedAccessToken' and refresh token '$anonymizedRefreshToken'")
} }

View file

@ -11,7 +11,6 @@ import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.core.coroutine.childScope import io.element.android.libraries.core.coroutine.childScope
import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.core.extensions.mapFailure
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
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.RoomAlias import io.element.android.libraries.matrix.api.core.RoomAlias
@ -84,7 +83,6 @@ class JoinedRustRoom(
private val coroutineDispatchers: CoroutineDispatchers, private val coroutineDispatchers: CoroutineDispatchers,
private val systemClock: SystemClock, private val systemClock: SystemClock,
private val roomContentForwarder: RoomContentForwarder, private val roomContentForwarder: RoomContentForwarder,
private val featureFlagService: FeatureFlagService,
) : JoinedRoom, BaseRoom by baseRoom { ) : JoinedRoom, BaseRoom by baseRoom {
// Create a dispatcher for all room methods... // Create a dispatcher for all room methods...
private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32) private val roomDispatcher = coroutineDispatchers.io.limitedParallelism(32)
@ -478,7 +476,6 @@ class JoinedRustRoom(
dispatcher = roomDispatcher, dispatcher = roomDispatcher,
roomContentForwarder = roomContentForwarder, roomContentForwarder = roomContentForwarder,
onNewSyncedEvent = onNewSyncedEvent, onNewSyncedEvent = onNewSyncedEvent,
featureFlagsService = featureFlagService,
) )
} }
} }

View file

@ -9,7 +9,6 @@ package io.element.android.libraries.matrix.impl.room
import io.element.android.appconfig.TimelineConfig import io.element.android.appconfig.TimelineConfig
import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.CoroutineDispatchers
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.matrix.api.core.DeviceId import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
@ -49,7 +48,6 @@ class RustRoomFactory(
private val innerRoomListService: InnerRoomListService, private val innerRoomListService: InnerRoomListService,
private val roomSyncSubscriber: RoomSyncSubscriber, private val roomSyncSubscriber: RoomSyncSubscriber,
private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory,
private val featureFlagService: FeatureFlagService,
private val roomMembershipObserver: RoomMembershipObserver, private val roomMembershipObserver: RoomMembershipObserver,
private val roomInfoMapper: RoomInfoMapper, private val roomInfoMapper: RoomInfoMapper,
) { ) {
@ -127,7 +125,6 @@ class RustRoomFactory(
liveInnerTimeline = timeline, liveInnerTimeline = timeline,
coroutineDispatchers = dispatchers, coroutineDispatchers = dispatchers,
systemClock = systemClock, systemClock = systemClock,
featureFlagService = featureFlagService,
) )
) )
} else { } else {

View file

@ -8,8 +8,6 @@
package io.element.android.libraries.matrix.impl.timeline package io.element.android.libraries.matrix.impl.timeline
import io.element.android.libraries.core.extensions.runCatchingExceptions import io.element.android.libraries.core.extensions.runCatchingExceptions
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
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.ProgressCallback import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -89,7 +87,6 @@ class RustTimeline(
private val coroutineScope: CoroutineScope, private val coroutineScope: CoroutineScope,
private val dispatcher: CoroutineDispatcher, private val dispatcher: CoroutineDispatcher,
private val roomContentForwarder: RoomContentForwarder, private val roomContentForwarder: RoomContentForwarder,
private val featureFlagsService: FeatureFlagService,
onNewSyncedEvent: () -> Unit, onNewSyncedEvent: () -> Unit,
) : Timeline { ) : Timeline {
private val _timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> = private val _timelineItems: MutableSharedFlow<List<MatrixTimelineItem>> =
@ -342,7 +339,6 @@ class RustTimeline(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
inReplyToEventId: EventId?, inReplyToEventId: EventId?,
): Result<MediaUploadHandler> { ): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOfNotNull(file, thumbnailFile)) { return sendAttachment(listOfNotNull(file, thumbnailFile)) {
inner.sendImage( inner.sendImage(
params = UploadParameters( params = UploadParameters(
@ -351,7 +347,7 @@ class RustTimeline(
formattedCaption = formattedCaption?.let { formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html) FormattedBody(body = it, format = MessageFormat.Html)
}, },
useSendQueue = useSendQueue, useSendQueue = true,
mentions = null, mentions = null,
inReplyTo = inReplyToEventId?.value, inReplyTo = inReplyToEventId?.value,
), ),
@ -371,7 +367,6 @@ class RustTimeline(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
inReplyToEventId: EventId?, inReplyToEventId: EventId?,
): Result<MediaUploadHandler> { ): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOfNotNull(file, thumbnailFile)) { return sendAttachment(listOfNotNull(file, thumbnailFile)) {
inner.sendVideo( inner.sendVideo(
params = UploadParameters( params = UploadParameters(
@ -380,7 +375,7 @@ class RustTimeline(
formattedCaption = formattedCaption?.let { formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html) FormattedBody(body = it, format = MessageFormat.Html)
}, },
useSendQueue = useSendQueue, useSendQueue = true,
mentions = null, mentions = null,
inReplyTo = inReplyToEventId?.value, inReplyTo = inReplyToEventId?.value,
), ),
@ -399,7 +394,6 @@ class RustTimeline(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
inReplyToEventId: EventId?, inReplyToEventId: EventId?,
): Result<MediaUploadHandler> { ): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) { return sendAttachment(listOf(file)) {
inner.sendAudio( inner.sendAudio(
params = UploadParameters( params = UploadParameters(
@ -408,7 +402,7 @@ class RustTimeline(
formattedCaption = formattedCaption?.let { formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html) FormattedBody(body = it, format = MessageFormat.Html)
}, },
useSendQueue = useSendQueue, useSendQueue = true,
mentions = null, mentions = null,
inReplyTo = inReplyToEventId?.value, inReplyTo = inReplyToEventId?.value,
), ),
@ -426,7 +420,6 @@ class RustTimeline(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
inReplyToEventId: EventId?, inReplyToEventId: EventId?,
): Result<MediaUploadHandler> { ): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) { return sendAttachment(listOf(file)) {
inner.sendFile( inner.sendFile(
params = UploadParameters( params = UploadParameters(
@ -435,7 +428,7 @@ class RustTimeline(
formattedCaption = formattedCaption?.let { formattedCaption = formattedCaption?.let {
FormattedBody(body = it, format = MessageFormat.Html) FormattedBody(body = it, format = MessageFormat.Html)
}, },
useSendQueue = useSendQueue, useSendQueue = true,
mentions = null, mentions = null,
inReplyTo = inReplyToEventId?.value, inReplyTo = inReplyToEventId?.value,
), ),
@ -489,7 +482,6 @@ class RustTimeline(
progressCallback: ProgressCallback?, progressCallback: ProgressCallback?,
inReplyToEventId: EventId?, inReplyToEventId: EventId?,
): Result<MediaUploadHandler> { ): Result<MediaUploadHandler> {
val useSendQueue = featureFlagsService.isFeatureEnabled(FeatureFlags.MediaUploadOnSendQueue)
return sendAttachment(listOf(file)) { return sendAttachment(listOf(file)) {
inner.sendVoiceMessage( inner.sendVoiceMessage(
params = UploadParameters( params = UploadParameters(
@ -497,7 +489,7 @@ class RustTimeline(
// Maybe allow a caption in the future? // Maybe allow a caption in the future?
caption = null, caption = null,
formattedCaption = null, formattedCaption = null,
useSendQueue = useSendQueue, useSendQueue = true,
mentions = null, mentions = null,
inReplyTo = inReplyToEventId?.value, inReplyTo = inReplyToEventId?.value,
), ),

View file

@ -8,7 +8,6 @@
package io.element.android.libraries.matrix.impl package io.element.android.libraries.matrix.impl
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiClient
import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeFfiSyncService
import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory import io.element.android.libraries.matrix.impl.room.FakeTimelineEventTypeFilterFactory
@ -67,6 +66,5 @@ class RustMatrixClientTest {
baseCacheDirectory = File(""), baseCacheDirectory = File(""),
clock = FakeSystemClock(), clock = FakeSystemClock(),
timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(), timelineEventTypeFilterFactory = FakeTimelineEventTypeFilterFactory(),
featureFlagService = FakeFeatureFlagService(),
) )
} }

View file

@ -10,8 +10,6 @@ package io.element.android.libraries.matrix.impl.timeline
import app.cash.turbine.test import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.featureflag.api.FeatureFlagService
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.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
@ -98,7 +96,6 @@ private fun TestScope.createRustTimeline(
coroutineScope: CoroutineScope = backgroundScope, coroutineScope: CoroutineScope = backgroundScope,
dispatcher: CoroutineDispatcher = testCoroutineDispatchers().io, dispatcher: CoroutineDispatcher = testCoroutineDispatchers().io,
roomContentForwarder: RoomContentForwarder = RoomContentForwarder(FakeFfiRoomListService()), roomContentForwarder: RoomContentForwarder = RoomContentForwarder(FakeFfiRoomListService()),
featureFlagsService: FeatureFlagService = FakeFeatureFlagService(),
onNewSyncedEvent: () -> Unit = {}, onNewSyncedEvent: () -> Unit = {},
): RustTimeline { ): RustTimeline {
return RustTimeline( return RustTimeline(
@ -109,7 +106,6 @@ private fun TestScope.createRustTimeline(
coroutineScope = coroutineScope, coroutineScope = coroutineScope,
dispatcher = dispatcher, dispatcher = dispatcher,
roomContentForwarder = roomContentForwarder, roomContentForwarder = roomContentForwarder,
featureFlagsService = featureFlagsService,
onNewSyncedEvent = onNewSyncedEvent, onNewSyncedEvent = onNewSyncedEvent,
) )
} }

View file

@ -91,7 +91,6 @@ import io.element.android.wysiwyg.compose.RichTextEditor
import io.element.android.wysiwyg.display.TextDisplay import io.element.android.wysiwyg.display.TextDisplay
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import uniffi.wysiwyg_composer.MenuAction import uniffi.wysiwyg_composer.MenuAction
import kotlin.time.Duration.Companion.seconds import kotlin.time.Duration.Companion.seconds
@ -101,7 +100,6 @@ fun TextComposer(
state: TextEditorState, state: TextEditorState,
voiceMessageState: VoiceMessageState, voiceMessageState: VoiceMessageState,
composerMode: MessageComposerMode, composerMode: MessageComposerMode,
enableVoiceMessages: Boolean,
onRequestFocus: () -> Unit, onRequestFocus: () -> Unit,
onSendMessage: () -> Unit, onSendMessage: () -> Unit,
onResetComposerMode: () -> Unit, onResetComposerMode: () -> Unit,
@ -141,8 +139,8 @@ fun TextComposer(
} }
val layoutModifier = modifier val layoutModifier = modifier
.fillMaxSize() .fillMaxSize()
.height(IntrinsicSize.Min) .height(IntrinsicSize.Min)
val composerOptionsButton: @Composable () -> Unit = remember(composerMode) { val composerOptionsButton: @Composable () -> Unit = remember(composerMode) {
@Composable { @Composable {
@ -171,22 +169,17 @@ fun TextComposer(
} else { } else {
stringResource(id = R.string.rich_text_editor_composer_placeholder) stringResource(id = R.string.rich_text_editor_composer_placeholder)
} }
val textInput: @Composable () -> Unit = if ((composerMode as? MessageComposerMode.Attachment)?.allowCaption == false) { val textInput: @Composable () -> Unit = when (state) {
{ is TextEditorState.Rich -> {
// No text input when in attachment mode and caption not allowed. val coroutineScope = rememberCoroutineScope()
} val view = LocalView.current
} else { remember(state.richTextEditorState, composerMode, onResetComposerMode, onError) {
when (state) { @Composable {
is TextEditorState.Rich -> { TextInputBox(
val coroutineScope = rememberCoroutineScope() modifier = Modifier
val view = LocalView.current
remember(state.richTextEditorState, composerMode, onResetComposerMode, onError) {
@Composable {
TextInputBox(
modifier = Modifier
.clickable( .clickable(
interactionSource = remember { MutableInteractionSource() }, interactionSource = remember { MutableInteractionSource() },
indication = null, indication = null,
) { ) {
coroutineScope.launch { coroutineScope.launch {
state.requestFocus() state.requestFocus()
@ -196,46 +189,45 @@ fun TextComposer(
.semantics { .semantics {
hideFromAccessibility() hideFromAccessibility()
}, },
composerMode = composerMode, composerMode = composerMode,
onResetComposerMode = onResetComposerMode, onResetComposerMode = onResetComposerMode,
isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(), isTextEmpty = state.richTextEditorState.messageHtml.isEmpty(),
) { ) {
RichTextEditor( RichTextEditor(
state = state.richTextEditorState, state = state.richTextEditorState,
placeholder = placeholder, placeholder = placeholder,
registerStateUpdates = true, registerStateUpdates = true,
modifier = Modifier modifier = Modifier
.padding(top = 6.dp, bottom = 6.dp) .padding(top = 6.dp, bottom = 6.dp)
.fillMaxWidth(), .fillMaxWidth(),
style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.richTextEditorState.hasFocus), style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.richTextEditorState.hasFocus),
resolveMentionDisplay = resolveMentionDisplay, resolveMentionDisplay = resolveMentionDisplay,
resolveRoomMentionDisplay = resolveAtRoomMentionDisplay, resolveRoomMentionDisplay = resolveAtRoomMentionDisplay,
onError = onError, onError = onError,
onRichContentSelected = onSelectRichContent, onRichContentSelected = onSelectRichContent,
onTyping = onTyping, onTyping = onTyping,
) )
}
} }
} }
} }
is TextEditorState.Markdown -> { }
@Composable { is TextEditorState.Markdown -> {
val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus()) @Composable {
TextInputBox( val style = ElementRichTextEditorStyle.composerStyle(hasFocus = state.hasFocus())
composerMode = composerMode, TextInputBox(
onResetComposerMode = onResetComposerMode, composerMode = composerMode,
isTextEmpty = state.state.text.value().isEmpty(), onResetComposerMode = onResetComposerMode,
) { isTextEmpty = state.state.text.value().isEmpty(),
MarkdownTextInput( ) {
state = state.state, MarkdownTextInput(
placeholder = placeholder, state = state.state,
placeholderColor = ElementTheme.colors.textSecondary, placeholder = placeholder,
onTyping = onTyping, placeholderColor = ElementTheme.colors.textSecondary,
onReceiveSuggestion = onReceiveSuggestion, onTyping = onTyping,
richTextEditorStyle = style, onReceiveSuggestion = onReceiveSuggestion,
onSelectRichContent = onSelectRichContent, richTextEditorStyle = style,
) onSelectRichContent = onSelectRichContent,
} )
} }
} }
} }
@ -273,7 +265,7 @@ fun TextComposer(
} }
val sendOrRecordButton = when { val sendOrRecordButton = when {
enableVoiceMessages && !canSendMessage -> !canSendMessage ->
when (voiceMessageState) { when (voiceMessageState) {
VoiceMessageState.Idle, VoiceMessageState.Idle,
is VoiceMessageState.Recording -> recordVoiceButton is VoiceMessageState.Recording -> recordVoiceButton
@ -288,7 +280,6 @@ fun TextComposer(
val endButtonA11y = endButtonA11y( val endButtonA11y = endButtonA11y(
composerMode = composerMode, composerMode = composerMode,
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
enableVoiceMessages = enableVoiceMessages,
canSendMessage = canSendMessage, canSendMessage = canSendMessage,
) )
@ -341,7 +332,6 @@ fun TextComposer(
} else { } else {
StandardLayout( StandardLayout(
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
enableVoiceMessages = enableVoiceMessages,
isRoomEncrypted = state.isRoomEncrypted, isRoomEncrypted = state.isRoomEncrypted,
modifier = layoutModifier, modifier = layoutModifier,
composerOptionsButton = composerOptionsButton, composerOptionsButton = composerOptionsButton,
@ -378,12 +368,11 @@ fun TextComposer(
private fun endButtonA11y( private fun endButtonA11y(
composerMode: MessageComposerMode, composerMode: MessageComposerMode,
voiceMessageState: VoiceMessageState, voiceMessageState: VoiceMessageState,
enableVoiceMessages: Boolean,
canSendMessage: Boolean, canSendMessage: Boolean,
): (SemanticsPropertyReceiver) -> Unit { ): (SemanticsPropertyReceiver) -> Unit {
val a11ySendButtonDescription = stringResource( val a11ySendButtonDescription = stringResource(
id = when { id = when {
enableVoiceMessages && !canSendMessage -> !canSendMessage ->
when (voiceMessageState) { when (voiceMessageState) {
VoiceMessageState.Idle, VoiceMessageState.Idle,
is VoiceMessageState.Recording -> if (voiceMessageState is VoiceMessageState.Recording) { is VoiceMessageState.Recording -> if (voiceMessageState is VoiceMessageState.Recording) {
@ -410,7 +399,6 @@ private fun endButtonA11y(
@Composable @Composable
private fun StandardLayout( private fun StandardLayout(
voiceMessageState: VoiceMessageState, voiceMessageState: VoiceMessageState,
enableVoiceMessages: Boolean,
isRoomEncrypted: Boolean?, isRoomEncrypted: Boolean?,
textInput: @Composable () -> Unit, textInput: @Composable () -> Unit,
composerOptionsButton: @Composable () -> Unit, composerOptionsButton: @Composable () -> Unit,
@ -427,12 +415,12 @@ private fun StandardLayout(
Spacer(Modifier.height(4.dp)) Spacer(Modifier.height(4.dp))
} }
Row(verticalAlignment = Alignment.Bottom) { Row(verticalAlignment = Alignment.Bottom) {
if (enableVoiceMessages && voiceMessageState !is VoiceMessageState.Idle) { if (voiceMessageState !is VoiceMessageState.Idle) {
if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) { if (voiceMessageState is VoiceMessageState.Preview || voiceMessageState is VoiceMessageState.Recording) {
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp) .padding(bottom = 5.dp, top = 5.dp, end = 3.dp, start = 3.dp)
.size(48.dp), .size(48.dp),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
voiceDeleteButton() voiceDeleteButton()
@ -442,8 +430,8 @@ private fun StandardLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp) .padding(bottom = 8.dp, top = 8.dp)
.weight(1f) .weight(1f)
) { ) {
voiceRecording() voiceRecording()
} }
@ -456,17 +444,17 @@ private fun StandardLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp, top = 8.dp) .padding(bottom = 8.dp, top = 8.dp)
.weight(1f) .weight(1f)
) { ) {
textInput() textInput()
} }
} }
Box( Box(
Modifier Modifier
.padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp) .padding(bottom = 5.dp, top = 5.dp, end = 6.dp, start = 6.dp)
.size(48.dp) .size(48.dp)
.clearAndSetSemantics(endButtonA11y), .clearAndSetSemantics(endButtonA11y),
contentAlignment = Alignment.Center, contentAlignment = Alignment.Center,
) { ) {
endButton() endButton()
@ -517,8 +505,8 @@ private fun TextFormattingLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(horizontal = 12.dp) .padding(horizontal = 12.dp)
) { ) {
textInput() textInput()
} }
@ -537,11 +525,11 @@ private fun TextFormattingLayout(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding( .padding(
start = 14.dp, start = 14.dp,
end = 6.dp, end = 6.dp,
) )
.clearAndSetSemantics(endButtonA11y) .clearAndSetSemantics(endButtonA11y)
) { ) {
sendButton() sendButton()
} }
@ -563,12 +551,12 @@ private fun TextInputBox(
Column( Column(
modifier = Modifier modifier = Modifier
.clip(roundedCorners) .clip(roundedCorners)
.border(0.5.dp, borderColor, roundedCorners) .border(0.5.dp, borderColor, roundedCorners)
.background(color = bgColor) .background(color = bgColor)
.requiredHeightIn(min = 42.dp) .requiredHeightIn(min = 42.dp)
.fillMaxSize() .fillMaxSize()
.then(modifier), .then(modifier),
) { ) {
if (composerMode is MessageComposerMode.Special) { if (composerMode is MessageComposerMode.Special) {
ComposerModeView( ComposerModeView(
@ -578,8 +566,8 @@ private fun TextInputBox(
} }
Box( Box(
modifier = Modifier modifier = Modifier
.padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp) .padding(top = 4.dp, bottom = 4.dp, start = 12.dp, end = 12.dp)
.then(Modifier.testTag(TestTags.textEditor)), .then(Modifier.testTag(TestTags.textEditor)),
contentAlignment = Alignment.CenterStart, contentAlignment = Alignment.CenterStart,
) { ) {
textInput() textInput()
@ -587,9 +575,9 @@ private fun TextInputBox(
var showBottomSheet by remember { mutableStateOf(false) } var showBottomSheet by remember { mutableStateOf(false) }
Icon( Icon(
modifier = Modifier modifier = Modifier
.clickable { showBottomSheet = true } .clickable { showBottomSheet = true }
.padding(horizontal = 8.dp, vertical = 4.dp) .padding(horizontal = 8.dp, vertical = 4.dp)
.align(Alignment.CenterEnd), .align(Alignment.CenterEnd),
imageVector = CompoundIcons.InfoSolid(), imageVector = CompoundIcons.InfoSolid(),
tint = ElementTheme.colors.iconCriticalPrimary, tint = ElementTheme.colors.iconCriticalPrimary,
contentDescription = null, contentDescription = null,
@ -631,12 +619,11 @@ private fun aTextEditorStateRichList(isRoomEncrypted: Boolean? = null) = persist
internal fun TextComposerSimplePreview() = ElementPreview { internal fun TextComposerSimplePreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList() items = aTextEditorStateMarkdownList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -646,12 +633,11 @@ internal fun TextComposerSimplePreview() = ElementPreview {
internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview { internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList(isRoomEncrypted = false), items = aTextEditorStateMarkdownList(isRoomEncrypted = false),
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -661,13 +647,12 @@ internal fun TextComposerSimpleNotEncryptedPreview() = ElementPreview {
internal fun TextComposerFormattingPreview() = ElementPreview { internal fun TextComposerFormattingPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
showTextFormatting = true, showTextFormatting = true,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -677,13 +662,12 @@ internal fun TextComposerFormattingPreview() = ElementPreview {
internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview { internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
showTextFormatting = true, showTextFormatting = true,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -693,12 +677,11 @@ internal fun TextComposerFormattingNotEncryptedPreview() = ElementPreview {
internal fun TextComposerEditPreview() = ElementPreview { internal fun TextComposerEditPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEdit(), composerMode = aMessageComposerModeEdit(),
enableVoiceMessages = true,
) )
} }
} }
@ -708,12 +691,11 @@ internal fun TextComposerEditPreview() = ElementPreview {
internal fun TextComposerEditNotEncryptedPreview() = ElementPreview { internal fun TextComposerEditNotEncryptedPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEdit(), composerMode = aMessageComposerModeEdit(),
enableVoiceMessages = true,
) )
} }
} }
@ -723,7 +705,7 @@ internal fun TextComposerEditNotEncryptedPreview() = ElementPreview {
internal fun TextComposerEditCaptionPreview() = ElementPreview { internal fun TextComposerEditCaptionPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
@ -731,7 +713,6 @@ internal fun TextComposerEditCaptionPreview() = ElementPreview {
// Set an existing caption so that the UI will be in edit caption mode // Set an existing caption so that the UI will be in edit caption mode
content = "An existing caption", content = "An existing caption",
), ),
enableVoiceMessages = false,
) )
} }
} }
@ -741,16 +722,14 @@ internal fun TextComposerEditCaptionPreview() = ElementPreview {
internal fun TextComposerAddCaptionPreview() = ElementPreview { internal fun TextComposerAddCaptionPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { index, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEditCaption( composerMode = aMessageComposerModeEditCaption(
// No caption so that the UI will be in add caption mode // No caption so that the UI will be in add caption mode
content = "", content = "",
showCompatibilityWarning = index == 0,
), ),
enableVoiceMessages = false,
) )
} }
} }
@ -760,12 +739,11 @@ internal fun TextComposerAddCaptionPreview() = ElementPreview {
internal fun MarkdownTextComposerEditPreview() = ElementPreview { internal fun MarkdownTextComposerEditPreview() = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateMarkdownList() items = aTextEditorStateMarkdownList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeEdit(), composerMode = aMessageComposerModeEdit(),
enableVoiceMessages = true,
) )
} }
} }
@ -775,14 +753,13 @@ internal fun MarkdownTextComposerEditPreview() = ElementPreview {
internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview { internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList() items = aTextEditorStateRichList()
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeReply( composerMode = aMessageComposerModeReply(
replyToDetails = inReplyToDetails, replyToDetails = inReplyToDetails,
), ),
enableVoiceMessages = true,
) )
} }
} }
@ -800,14 +777,13 @@ internal fun TextComposerReplyPreview(@PreviewParameter(InReplyToDetailsProvider
internal fun TextComposerReplyNotEncryptedPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview { internal fun TextComposerReplyNotEncryptedPreview(@PreviewParameter(InReplyToDetailsProvider::class) inReplyToDetails: InReplyToDetails) = ElementPreview {
PreviewColumn( PreviewColumn(
items = aTextEditorStateRichList(isRoomEncrypted = false) items = aTextEditorStateRichList(isRoomEncrypted = false)
) { _, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = aMessageComposerModeReply( composerMode = aMessageComposerModeReply(
replyToDetails = inReplyToDetails, replyToDetails = inReplyToDetails,
), ),
enableVoiceMessages = true,
) )
} }
} }
@ -817,16 +793,12 @@ internal fun TextComposerReplyNotEncryptedPreview(@PreviewParameter(InReplyToDet
internal fun TextComposerCaptionPreview() = ElementPreview { internal fun TextComposerCaptionPreview() = ElementPreview {
val list = aTextEditorStateMarkdownList() val list = aTextEditorStateMarkdownList()
PreviewColumn( PreviewColumn(
items = (list + aTextEditorStateMarkdown(initialText = "NO_CAPTION", initialFocus = true)).toPersistentList() items = list,
) { index, textEditorState -> ) { textEditorState ->
ATextComposer( ATextComposer(
state = textEditorState, state = textEditorState,
voiceMessageState = VoiceMessageState.Idle, voiceMessageState = VoiceMessageState.Idle,
composerMode = MessageComposerMode.Attachment( composerMode = MessageComposerMode.Attachment,
allowCaption = index < list.size,
showCaptionCompatibilityWarning = index == 0,
),
enableVoiceMessages = false,
) )
} }
} }
@ -862,12 +834,11 @@ internal fun TextComposerVoicePreview() = ElementPreview {
playbackProgress = 0.0f playbackProgress = 0.0f
), ),
) )
) { _, voiceMessageState -> ) { voiceMessageState ->
ATextComposer( ATextComposer(
state = aTextEditorStateRich(initialFocus = true), state = aTextEditorStateRich(initialFocus = true),
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -903,12 +874,11 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
playbackProgress = 0.0f playbackProgress = 0.0f
), ),
) )
) { _, voiceMessageState -> ) { voiceMessageState ->
ATextComposer( ATextComposer(
state = aTextEditorStateRich(initialFocus = true, isRoomEncrypted = false), state = aTextEditorStateRich(initialFocus = true, isRoomEncrypted = false),
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
composerMode = MessageComposerMode.Normal, composerMode = MessageComposerMode.Normal,
enableVoiceMessages = true,
) )
} }
} }
@ -916,15 +886,15 @@ internal fun TextComposerVoiceNotEncryptedPreview() = ElementPreview {
@Composable @Composable
private fun <T> PreviewColumn( private fun <T> PreviewColumn(
items: ImmutableList<T>, items: ImmutableList<T>,
view: @Composable (Int, T) -> Unit, view: @Composable (T) -> Unit,
) { ) {
Column { Column {
items.forEachIndexed { index, item -> items.forEach { item ->
HorizontalDivider() HorizontalDivider()
Box( Box(
modifier = Modifier.height(IntrinsicSize.Min) modifier = Modifier.height(IntrinsicSize.Min)
) { ) {
view(index, item) view(item)
} }
} }
} }
@ -935,7 +905,6 @@ private fun ATextComposer(
state: TextEditorState, state: TextEditorState,
voiceMessageState: VoiceMessageState, voiceMessageState: VoiceMessageState,
composerMode: MessageComposerMode, composerMode: MessageComposerMode,
enableVoiceMessages: Boolean,
showTextFormatting: Boolean = false, showTextFormatting: Boolean = false,
) { ) {
TextComposer( TextComposer(
@ -943,7 +912,6 @@ private fun ATextComposer(
showTextFormatting = showTextFormatting, showTextFormatting = showTextFormatting,
voiceMessageState = voiceMessageState, voiceMessageState = voiceMessageState,
composerMode = composerMode, composerMode = composerMode,
enableVoiceMessages = enableVoiceMessages,
onRequestFocus = {}, onRequestFocus = {},
onSendMessage = {}, onSendMessage = {},
onResetComposerMode = {}, onResetComposerMode = {},
@ -973,11 +941,9 @@ fun aMessageComposerModeEdit(
fun aMessageComposerModeEditCaption( fun aMessageComposerModeEditCaption(
eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(), eventOrTransactionId: EventOrTransactionId = EventId("$1234").toEventOrTransactionId(),
content: String, content: String,
showCompatibilityWarning: Boolean = false,
) = MessageComposerMode.EditCaption( ) = MessageComposerMode.EditCaption(
eventOrTransactionId = eventOrTransactionId, eventOrTransactionId = eventOrTransactionId,
content = content, content = content,
showCaptionCompatibilityWarning = showCompatibilityWarning,
) )
fun aMessageComposerModeReply( fun aMessageComposerModeReply(

View file

@ -18,10 +18,7 @@ import io.element.android.libraries.matrix.ui.messages.reply.eventId
sealed interface MessageComposerMode { sealed interface MessageComposerMode {
data object Normal : MessageComposerMode data object Normal : MessageComposerMode
data class Attachment( data object Attachment : MessageComposerMode
val allowCaption: Boolean,
val showCaptionCompatibilityWarning: Boolean,
) : MessageComposerMode
sealed interface Special : MessageComposerMode sealed interface Special : MessageComposerMode
@ -33,7 +30,6 @@ sealed interface MessageComposerMode {
data class EditCaption( data class EditCaption(
val eventOrTransactionId: EventOrTransactionId, val eventOrTransactionId: EventOrTransactionId,
val content: String, val content: String,
val showCaptionCompatibilityWarning: Boolean,
) : Special ) : Special
data class Reply( data class Reply(
@ -58,8 +54,8 @@ sealed interface MessageComposerMode {
fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean { fun MessageComposerMode.showCaptionCompatibilityWarning(): Boolean {
return when (this) { return when (this) {
is MessageComposerMode.Attachment -> showCaptionCompatibilityWarning is MessageComposerMode.Attachment -> true
is MessageComposerMode.EditCaption -> showCaptionCompatibilityWarning && content.isEmpty() is MessageComposerMode.EditCaption -> content.isEmpty()
else -> false else -> false
} }
} }

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dbeb0287f75afc662c28cca13fe39f9d24e67ee5b507cf976324d5d9dd4c381b
size 83505

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ba8fe03e073a2279c68c49c25388568182ddb1da1e20d7df1ce72d150df51d86 oid sha256:b8119afe2e6a12bd41f8e838461c9e3f311ed0b5e5ce3c524a70226feb120996
size 395881 size 72836

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:400426592b0020e5de30899574ce0abb70cebccbcff10c43728ee6dae4a421f3 oid sha256:8e0cf2a3fc5f92c5b7e8caff9e60ad9495ba1703dbb160aa64f9fd0a1bd4822f
size 400259 size 406105

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:b8119afe2e6a12bd41f8e838461c9e3f311ed0b5e5ce3c524a70226feb120996 oid sha256:dbeb0287f75afc662c28cca13fe39f9d24e67ee5b507cf976324d5d9dd4c381b
size 72836 size 83505

View file

@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8e0cf2a3fc5f92c5b7e8caff9e60ad9495ba1703dbb160aa64f9fd0a1bd4822f
size 406105

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:55a3aa36196ff755f207e47abb3060a4727a6baa79fe02ce5f5a62d3648619b5 oid sha256:4d33345e7d8cb2a55e6d291ae9dcabe66c8ec1ea4124ebc32b679566e585ca0e
size 53661 size 54020

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:2c8cd2e6427bc0ffacca88900511cb3761e7bf57fcafb3dbc9ec7f519ec8349d oid sha256:a6af3cf20ad9b57abbe61107f758c143a676dec960fdf583b517d18e404411a3
size 51678 size 51953

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:9d9c7a841dbe183f84c8527a8745d30aaeee6d7b4aa8a472794b7b4d739b21cd oid sha256:936a9e86eddcb00e20de8af1fe60ae2153c3dfa384f8070466026969a34eb057
size 47268 size 45074

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e8275ab2ec26aed30fc162be1d5b49d6b9e9d73c303b00220ac91306dd04280e oid sha256:dee196ccc9872a15897426f2581c178ff92a393136aefc5f7342ba1984d8e418
size 45645 size 43492

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:59d57f02efee0ab0f413e3d14d22d069862143e9d6d69fb29fa6972348356016 oid sha256:dea3e3f516afb98543f458e0db6472a7157428e25d8170bee6abbc505ece7bd7
size 52332 size 52677

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:ae8804def7f6922e29ee4564f96d490d233909d6383dc5b92ed091d239ffb609 oid sha256:8ff7e86b3aefc4092514c0312a91747ac0a8a3f90234ca9774cd52ab90eeab17
size 50797 size 51072