Merge pull request #5451 from element-hq/feature/bma/spaceAnnoucement
Space annoucement
This commit is contained in:
commit
97ae89ed96
37 changed files with 945 additions and 13 deletions
|
|
@ -44,6 +44,7 @@ dependencies {
|
|||
|
||||
implementation(libs.coil)
|
||||
|
||||
implementation(projects.features.announcement.api)
|
||||
implementation(projects.features.ftue.api)
|
||||
implementation(projects.features.share.api)
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import io.element.android.appnav.intent.ResolvedIntent
|
|||
import io.element.android.appnav.root.RootNavStateFlowFactory
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.appnav.root.RootView
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.login.api.LoginParams
|
||||
import io.element.android.features.login.api.accesscontrol.AccountProviderAccessControl
|
||||
import io.element.android.features.rageshake.api.bugreport.BugReportEntryPoint
|
||||
|
|
@ -64,7 +65,9 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.parcelize.Parcelize
|
||||
import timber.log.Timber
|
||||
|
||||
@ContributesNode(AppScope::class) @AssistedInject class RootFlowNode(
|
||||
@ContributesNode(AppScope::class)
|
||||
@AssistedInject
|
||||
class RootFlowNode(
|
||||
@Assisted val buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
private val sessionStore: SessionStore,
|
||||
|
|
@ -79,6 +82,7 @@ import timber.log.Timber
|
|||
private val oidcActionFlow: OidcActionFlow,
|
||||
private val bugReporter: BugReporter,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val announcementService: AnnouncementService,
|
||||
) : BaseFlowNode<RootFlowNode.NavTarget>(
|
||||
backstack = BackStack(
|
||||
initialElement = NavTarget.SplashScreen,
|
||||
|
|
@ -185,6 +189,7 @@ import timber.log.Timber
|
|||
}
|
||||
}
|
||||
BackstackView(transitionHandler = transitionHandler)
|
||||
announcementService.Render(Modifier)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
13
features/announcement/api/build.gradle.kts
Normal file
13
features/announcement/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright 2025 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-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.announcement.api"
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.api
|
||||
|
||||
enum class Announcement {
|
||||
Space,
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
interface AnnouncementService {
|
||||
suspend fun showAnnouncement(announcement: Announcement)
|
||||
|
||||
@Composable
|
||||
fun Render(
|
||||
modifier: Modifier,
|
||||
)
|
||||
}
|
||||
37
features/announcement/impl/build.gradle.kts
Normal file
37
features/announcement/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright 2025 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-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.announcement.impl"
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
api(projects.features.announcement.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Inject
|
||||
class AnnouncementPresenter(
|
||||
private val announcementStore: AnnouncementStore,
|
||||
) : Presenter<AnnouncementState> {
|
||||
@Composable
|
||||
override fun present(): AnnouncementState {
|
||||
val showSpaceAnnouncement by remember {
|
||||
announcementStore.spaceAnnouncementFlow().map {
|
||||
it == AnnouncementStore.SpaceAnnouncement.Show
|
||||
}
|
||||
}.collectAsState(false)
|
||||
return AnnouncementState(
|
||||
showSpaceAnnouncement = showSpaceAnnouncement,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl
|
||||
|
||||
data class AnnouncementState(
|
||||
val showSpaceAnnouncement: Boolean,
|
||||
)
|
||||
|
||||
fun anAnnouncementState(
|
||||
showSpaceAnnouncement: Boolean = false,
|
||||
) = AnnouncementState(
|
||||
showSpaceAnnouncement = showSpaceAnnouncement,
|
||||
)
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementView
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@Inject
|
||||
class DefaultAnnouncementService(
|
||||
private val announcementStore: AnnouncementStore,
|
||||
private val announcementPresenter: Presenter<AnnouncementState>,
|
||||
private val spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState>,
|
||||
) : AnnouncementService {
|
||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||
when (announcement) {
|
||||
Announcement.Space -> showSpaceAnnouncement()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showSpaceAnnouncement() {
|
||||
val currentValue = announcementStore.spaceAnnouncementFlow().first()
|
||||
if (currentValue == AnnouncementStore.SpaceAnnouncement.NeverShown) {
|
||||
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Render(modifier: Modifier) {
|
||||
val announcementState = announcementPresenter.present()
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
AnimatedVisibility(
|
||||
visible = announcementState.showSpaceAnnouncement,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
) {
|
||||
val spaceAnnouncementState = spaceAnnouncementPresenter.present()
|
||||
SpaceAnnouncementView(
|
||||
state = spaceAnnouncementState,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.di
|
||||
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.BindingContainer
|
||||
import dev.zacsweers.metro.Binds
|
||||
import dev.zacsweers.metro.ContributesTo
|
||||
import io.element.android.features.announcement.impl.AnnouncementPresenter
|
||||
import io.element.android.features.announcement.impl.AnnouncementState
|
||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementPresenter
|
||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@BindingContainer
|
||||
interface AnnouncementModule {
|
||||
@Binds
|
||||
fun bindAnnouncementPresenter(presenter: AnnouncementPresenter): Presenter<AnnouncementState>
|
||||
|
||||
@Binds
|
||||
fun bindSpaceAnnouncementPresenter(presenter: SpaceAnnouncementPresenter): Presenter<SpaceAnnouncementState>
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
sealed interface SpaceAnnouncementEvents {
|
||||
data object Continue : SpaceAnnouncementEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore.SpaceAnnouncement
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Inject
|
||||
class SpaceAnnouncementPresenter(
|
||||
private val announcementStore: AnnouncementStore,
|
||||
) : Presenter<SpaceAnnouncementState> {
|
||||
@Composable
|
||||
override fun present(): SpaceAnnouncementState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun handleEvents(event: SpaceAnnouncementEvents) {
|
||||
when (event) {
|
||||
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
||||
announcementStore.setSpaceAnnouncementValue(SpaceAnnouncement.Shown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SpaceAnnouncementState(
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
data class SpaceAnnouncementState(
|
||||
val eventSink: (SpaceAnnouncementEvents) -> Unit
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
|
||||
open class SpaceAnnouncementStateProvider : PreviewParameterProvider<SpaceAnnouncementState> {
|
||||
override val values: Sequence<SpaceAnnouncementState>
|
||||
get() = sequenceOf(
|
||||
aSpaceAnnouncementState(),
|
||||
)
|
||||
}
|
||||
|
||||
fun aSpaceAnnouncementState(
|
||||
eventSink: (SpaceAnnouncementEvents) -> Unit = {},
|
||||
) = SpaceAnnouncementState(
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.features.announcement.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Button
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* Ref: https://www.figma.com/design/kcnHxunG1LDWXsJhaNuiHz/ER-145--Spaces-on-Element-X?node-id=4593-40181
|
||||
*/
|
||||
@Composable
|
||||
fun SpaceAnnouncementView(
|
||||
state: SpaceAnnouncementState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val eventSink = state.eventSink
|
||||
|
||||
fun onContinue() {
|
||||
eventSink(SpaceAnnouncementEvents.Continue)
|
||||
}
|
||||
|
||||
BackHandler(onBack = ::onContinue)
|
||||
HeaderFooterPage(
|
||||
modifier = modifier,
|
||||
isScrollable = true,
|
||||
contentPadding = PaddingValues(top = 24.dp, start = 16.dp, end = 16.dp, bottom = 24.dp),
|
||||
header = {
|
||||
SpaceAnnouncementHeader()
|
||||
},
|
||||
content = {
|
||||
SpaceAnnouncementContent(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
)
|
||||
},
|
||||
footer = {
|
||||
SpaceAnnouncementFooter(
|
||||
onContinue = ::onContinue,
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceAnnouncementHeader(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = modifier.padding(top = 16.dp, bottom = 16.dp),
|
||||
title = stringResource(id = R.string.screen_space_announcement_title),
|
||||
showBetaLabel = true,
|
||||
subTitle = stringResource(id = R.string.screen_space_announcement_subtitle),
|
||||
iconStyle = BigIcon.Style.Default(
|
||||
vectorIcon = CompoundIcons.WorkspaceSolid(),
|
||||
usePrimaryTint = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceAnnouncementContent(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
) {
|
||||
InfoListOrganism(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
items = persistentListOf(
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_space_announcement_item1),
|
||||
iconVector = CompoundIcons.VisibilityOn(),
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_space_announcement_item2),
|
||||
iconVector = CompoundIcons.Email(),
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_space_announcement_item3),
|
||||
iconVector = CompoundIcons.Search(),
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_space_announcement_item4),
|
||||
iconVector = CompoundIcons.Explore(),
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_space_announcement_item5),
|
||||
iconVector = CompoundIcons.Leave(),
|
||||
),
|
||||
),
|
||||
textStyle = ElementTheme.typography.fontBodyLgMedium,
|
||||
iconTint = ElementTheme.colors.iconSecondary,
|
||||
iconSize = 24.dp
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 16.dp),
|
||||
text = stringResource(id = R.string.screen_space_announcement_notice),
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SpaceAnnouncementFooter(
|
||||
onContinue: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
) {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_continue),
|
||||
onClick = onContinue,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun SpaceAnnouncementViewPreview(@PreviewParameter(SpaceAnnouncementStateProvider::class) state: SpaceAnnouncementState) = ElementPreview {
|
||||
SpaceAnnouncementView(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AnnouncementStore {
|
||||
suspend fun setSpaceAnnouncementValue(value: SpaceAnnouncement)
|
||||
fun spaceAnnouncementFlow(): Flow<SpaceAnnouncement>
|
||||
|
||||
suspend fun reset()
|
||||
|
||||
enum class SpaceAnnouncement {
|
||||
NeverShown,
|
||||
Show,
|
||||
Shown,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.store
|
||||
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.intPreferencesKey
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@Inject
|
||||
class DefaultAnnouncementStore(
|
||||
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : AnnouncementStore {
|
||||
private val store = preferenceDataStoreFactory.create("elementx_announcement")
|
||||
|
||||
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
||||
store.edit {
|
||||
it[spaceAnnouncementKey] = value.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
||||
return store.data.map { prefs ->
|
||||
val ordinal = prefs[spaceAnnouncementKey] ?: AnnouncementStore.SpaceAnnouncement.NeverShown.ordinal
|
||||
AnnouncementStore.SpaceAnnouncement.entries.getOrElse(ordinal) { AnnouncementStore.SpaceAnnouncement.NeverShown }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
store.edit { it.clear() }
|
||||
}
|
||||
}
|
||||
11
features/announcement/impl/src/main/res/values/localazy.xml
Normal file
11
features/announcement/impl/src/main/res/values/localazy.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_space_announcement_item1">"View spaces you\'ve created or joined"</string>
|
||||
<string name="screen_space_announcement_item2">"Accept or decline invites to spaces"</string>
|
||||
<string name="screen_space_announcement_item3">"Discover any rooms you can join in your spaces"</string>
|
||||
<string name="screen_space_announcement_item4">"Join public spaces"</string>
|
||||
<string name="screen_space_announcement_item5">"Leave any spaces you’ve joined"</string>
|
||||
<string name="screen_space_announcement_notice">"Creating and managing spaces is coming soon."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Welcome to the beta version of Spaces! With this first version you can:"</string>
|
||||
<string name="screen_space_announcement_title">"Introducing Spaces"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class AnnouncementPresenterTest {
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createAnnouncementPresenter()
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.showSpaceAnnouncement).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - showSpaceAnnouncement value depends on the value in the store`() = runTest {
|
||||
val store = InMemoryAnnouncementStore()
|
||||
val presenter = createAnnouncementPresenter(
|
||||
announcementStore = store,
|
||||
)
|
||||
presenter.test {
|
||||
val state = awaitItem()
|
||||
assertThat(state.showSpaceAnnouncement).isFalse()
|
||||
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Show)
|
||||
val updatedState = awaitItem()
|
||||
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
||||
store.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAnnouncementPresenter(
|
||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||
) = AnnouncementPresenter(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.impl.spaces.SpaceAnnouncementState
|
||||
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultAnnouncementServiceTest {
|
||||
@Test
|
||||
fun `when showing Space announcement, space announcement is set to show only if it was never shown`() = runTest {
|
||||
val announcementStore = InMemoryAnnouncementStore()
|
||||
val sut = createDefaultAnnouncementService(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
||||
sut.showAnnouncement(Announcement.Space)
|
||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Show)
|
||||
// Simulate user close the announcement
|
||||
announcementStore.setSpaceAnnouncementValue(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||
// Entering again the space tab should not change the value
|
||||
sut.showAnnouncement(Announcement.Space)
|
||||
assertThat(announcementStore.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||
}
|
||||
|
||||
private fun createDefaultAnnouncementService(
|
||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||
announcementPresenter: Presenter<AnnouncementState> = Presenter { anAnnouncementState() },
|
||||
spaceAnnouncementPresenter: Presenter<SpaceAnnouncementState> = Presenter { aSpaceAnnouncementState() },
|
||||
) = DefaultAnnouncementService(
|
||||
announcementStore = announcementStore,
|
||||
announcementPresenter = announcementPresenter,
|
||||
spaceAnnouncementPresenter = spaceAnnouncementPresenter,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.features.announcement.impl.store.InMemoryAnnouncementStore
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class SpaceAnnouncementPresenterTest {
|
||||
@Test
|
||||
fun `present - when user continues, the store is updated`() = runTest {
|
||||
val store = InMemoryAnnouncementStore()
|
||||
val presenter = createSpaceAnnouncementPresenter(
|
||||
announcementStore = store,
|
||||
)
|
||||
presenter.test {
|
||||
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.NeverShown)
|
||||
val state = awaitItem()
|
||||
state.eventSink(SpaceAnnouncementEvents.Continue)
|
||||
assertThat(store.spaceAnnouncementFlow().first()).isEqualTo(AnnouncementStore.SpaceAnnouncement.Shown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSpaceAnnouncementPresenter(
|
||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||
) = SpaceAnnouncementPresenter(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.spaces
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.pressBackKey
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class SpaceAnnouncementViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on back sends a SpaceAnnouncementEvents`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
||||
rule.setSpaceAnnouncementView(
|
||||
aSpaceAnnouncementState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.pressBackKey()
|
||||
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Continue sends a SpaceAnnouncementEvents`() {
|
||||
val eventsRecorder = EventsRecorder<SpaceAnnouncementEvents>()
|
||||
rule.setSpaceAnnouncementView(
|
||||
aSpaceAnnouncementState(
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(SpaceAnnouncementEvents.Continue)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceAnnouncementView(
|
||||
state: SpaceAnnouncementState,
|
||||
) {
|
||||
setContent {
|
||||
SpaceAnnouncementView(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright 2025 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.announcement.impl.store
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class InMemoryAnnouncementStore(
|
||||
initialSpaceAnnouncement: AnnouncementStore.SpaceAnnouncement = AnnouncementStore.SpaceAnnouncement.NeverShown,
|
||||
) : AnnouncementStore {
|
||||
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncement)
|
||||
override suspend fun setSpaceAnnouncementValue(value: AnnouncementStore.SpaceAnnouncement) {
|
||||
spaceAnnouncement.value = value
|
||||
}
|
||||
|
||||
override fun spaceAnnouncementFlow(): Flow<AnnouncementStore.SpaceAnnouncement> {
|
||||
return spaceAnnouncement.asStateFlow()
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
spaceAnnouncement.value = AnnouncementStore.SpaceAnnouncement.NeverShown
|
||||
}
|
||||
}
|
||||
19
features/announcement/test/build.gradle.kts
Normal file
19
features/announcement/test/build.gradle.kts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2025 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-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.announcement.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.features.announcement.api)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.tests.testutils)
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.rageshake.test.logs
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
|
||||
class FakeAnnouncementService(
|
||||
val showAnnouncementResult: (Announcement) -> Unit = { lambdaError() },
|
||||
val renderResult: (Modifier) -> Unit = { lambdaError() },
|
||||
) : AnnouncementService {
|
||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||
showAnnouncementResult(announcement)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Render(modifier: Modifier) {
|
||||
renderResult(modifier)
|
||||
}
|
||||
}
|
||||
|
|
@ -45,6 +45,7 @@ dependencies {
|
|||
implementation(projects.libraries.permissions.noop)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.features.announcement.api)
|
||||
implementation(projects.features.invite.api)
|
||||
implementation(projects.features.networkmonitor.api)
|
||||
implementation(projects.features.logout.api)
|
||||
|
|
@ -60,6 +61,7 @@ dependencies {
|
|||
api(projects.features.home.api)
|
||||
|
||||
testCommonDependencies(libs, true)
|
||||
testImplementation(projects.features.announcement.test)
|
||||
testImplementation(projects.features.invite.test)
|
||||
testImplementation(projects.features.logout.test)
|
||||
testImplementation(projects.features.networkmonitor.test)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import dev.zacsweers.metro.Inject
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.home.impl.roomlist.RoomListState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
|
|
@ -47,6 +49,7 @@ class HomePresenter(
|
|||
private val rageshakeFeatureAvailability: RageshakeFeatureAvailability,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val sessionStore: SessionStore,
|
||||
private val announcementService: AnnouncementService,
|
||||
) : Presenter<HomeState> {
|
||||
private val currentUserWithNeighborsBuilder = CurrentUserWithNeighborsBuilder()
|
||||
|
||||
|
|
@ -84,7 +87,10 @@ class HomePresenter(
|
|||
|
||||
fun handleEvents(event: HomeEvents) {
|
||||
when (event) {
|
||||
is HomeEvents.SelectHomeNavigationBarItem -> {
|
||||
is HomeEvents.SelectHomeNavigationBarItem -> coroutineState.launch {
|
||||
if (event.item == HomeNavigationBarItem.Spaces) {
|
||||
announcementService.showAnnouncement(Announcement.Space)
|
||||
}
|
||||
currentHomeNavigationBarItemOrdinal = event.item.ordinal
|
||||
}
|
||||
is HomeEvents.SwitchToAccount -> coroutineState.launch {
|
||||
|
|
|
|||
|
|
@ -11,11 +11,14 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.features.announcement.api.AnnouncementService
|
||||
import io.element.android.features.home.impl.roomlist.aRoomListState
|
||||
import io.element.android.features.home.impl.spaces.HomeSpacesState
|
||||
import io.element.android.features.home.impl.spaces.aHomeSpacesState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.rageshake.api.RageshakeFeatureAvailability
|
||||
import io.element.android.features.rageshake.test.logs.FakeAnnouncementService
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
|
|
@ -37,6 +40,8 @@ import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
|||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||
import io.element.android.tests.testutils.MutablePresenter
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -165,10 +170,14 @@ class HomePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - NavigationBar change`() = runTest {
|
||||
val showAnnouncementResult = lambdaRecorder<Announcement, Unit> { }
|
||||
val presenter = createHomePresenter(
|
||||
sessionStore = InMemorySessionStore(
|
||||
updateUserProfileResult = { _, _, _ -> },
|
||||
),
|
||||
announcementService = FakeAnnouncementService(
|
||||
showAnnouncementResult = showAnnouncementResult,
|
||||
)
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -178,6 +187,8 @@ class HomePresenterTest {
|
|||
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
|
||||
showAnnouncementResult.assertions().isCalledOnce()
|
||||
.with(value(Announcement.Space))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -192,6 +203,9 @@ class HomePresenterTest {
|
|||
initialState = mapOf(FeatureFlags.Space.key to true),
|
||||
),
|
||||
homeSpacesPresenter = homeSpacesPresenter,
|
||||
announcementService = FakeAnnouncementService(
|
||||
showAnnouncementResult = {},
|
||||
)
|
||||
)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
|
|
@ -222,6 +236,7 @@ internal fun createHomePresenter(
|
|||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
homeSpacesPresenter: Presenter<HomeSpacesState> = Presenter { aHomeSpacesState() },
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
announcementService: AnnouncementService = FakeAnnouncementService(),
|
||||
) = HomePresenter(
|
||||
client = client,
|
||||
syncService = syncService,
|
||||
|
|
@ -233,4 +248,5 @@ internal fun createHomePresenter(
|
|||
rageshakeFeatureAvailability = rageshakeFeatureAvailability,
|
||||
featureFlagService = featureFlagService,
|
||||
sessionStore = sessionStore,
|
||||
announcementService = announcementService,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2025 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.libraries.designsystem.atomic.atoms
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
|
||||
@Composable
|
||||
fun BetaLabel(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val shape = RoundedCornerShape(size = 6.dp)
|
||||
Text(
|
||||
modifier = modifier
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = ElementTheme.colors.borderInfoSubtle,
|
||||
shape = shape,
|
||||
)
|
||||
.background(
|
||||
color = ElementTheme.colors.bgInfoSubtle,
|
||||
shape = shape,
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = "BETA",
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
color = ElementTheme.colors.textInfoPrimary,
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun BetaLabelPreview() = ElementPreview {
|
||||
BetaLabel()
|
||||
}
|
||||
|
|
@ -7,7 +7,9 @@
|
|||
|
||||
package io.element.android.libraries.designsystem.atomic.molecules
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
|
|
@ -20,6 +22,7 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.atomic.atoms.BetaLabel
|
||||
import io.element.android.libraries.designsystem.components.BigIcon
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
|
|
@ -32,6 +35,7 @@ import io.element.android.libraries.designsystem.theme.components.Text
|
|||
* @param subTitle the subtitle to display
|
||||
* @param iconStyle the style of the [BigIcon] to display
|
||||
* @param modifier the modifier to apply to this layout
|
||||
* @param showBetaLabel whether to show a "BETA" label next to the title
|
||||
*/
|
||||
@Composable
|
||||
fun IconTitleSubtitleMolecule(
|
||||
|
|
@ -39,6 +43,7 @@ fun IconTitleSubtitleMolecule(
|
|||
subTitle: String?,
|
||||
iconStyle: BigIcon.Style,
|
||||
modifier: Modifier = Modifier,
|
||||
showBetaLabel: Boolean = false,
|
||||
) {
|
||||
Column(modifier) {
|
||||
BigIcon(
|
||||
|
|
@ -46,17 +51,25 @@ fun IconTitleSubtitleMolecule(
|
|||
style = iconStyle,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.semantics {
|
||||
heading()
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally)
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
modifier = Modifier
|
||||
.semantics {
|
||||
heading()
|
||||
},
|
||||
textAlign = TextAlign.Center,
|
||||
style = ElementTheme.typography.fontHeadingMdBold,
|
||||
color = ElementTheme.colors.textPrimary,
|
||||
)
|
||||
if (showBetaLabel) {
|
||||
BetaLabel()
|
||||
}
|
||||
}
|
||||
if (subTitle != null) {
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -53,11 +53,13 @@ object BigIcon {
|
|||
* @param vectorIcon the [ImageVector] to display
|
||||
* @param contentDescription the content description of the icon, if any. It defaults to `null`
|
||||
* @param useCriticalTint whether the icon and background should be rendered using critical tint
|
||||
* @param usePrimaryTint whether the icon should be rendered using primary tint
|
||||
*/
|
||||
data class Default(
|
||||
val vectorIcon: ImageVector,
|
||||
val contentDescription: String? = null,
|
||||
val useCriticalTint: Boolean = false,
|
||||
val usePrimaryTint: Boolean = false,
|
||||
) : Style
|
||||
|
||||
/**
|
||||
|
|
@ -143,6 +145,8 @@ object BigIcon {
|
|||
val iconTint = when (style) {
|
||||
is Style.Default -> if (style.useCriticalTint) {
|
||||
ElementTheme.colors.iconCriticalPrimary
|
||||
} else if (style.usePrimaryTint) {
|
||||
ElementTheme.colors.iconPrimary
|
||||
} else {
|
||||
ElementTheme.colors.iconSecondary
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const val A_PASSWORD = "password"
|
|||
const val A_PASSPHRASE = "passphrase"
|
||||
const val A_SECRET = "secret"
|
||||
const val AN_APPLICATION_NAME = "AppName"
|
||||
const val AN_APPLICATION_NAME_DESKTOP = "AppNameDesktop"
|
||||
|
||||
val A_USER_ID = UserId("@alice:server.org")
|
||||
val A_USER_ID_2 = UserId("@bob:server.org")
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:780559f9a0677b007c93f0c24bef94ab63c89f2d8c124084557c5c8dec0ea3d6
|
||||
size 60500
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:97e068729df68971b35727236873cc7954ae5e63890f255948fb4ccd98b3ae2f
|
||||
size 59572
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:e6453b165bca4b509d7a2e1749e0534f5f12465de3b32bb0513cf377021a57a5
|
||||
size 5080
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2431e2c666af3353ae85d9a101d8dde1378b4fa3d1828ff23a7f4753f6fb2725
|
||||
size 5112
|
||||
|
|
@ -22,6 +22,12 @@
|
|||
"settings_rageshake.*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : ":features:announcement:impl",
|
||||
"includeRegex" : [
|
||||
"screen\\.space_announcement\\..*"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name" : ":features:logout:impl",
|
||||
"includeRegex" : [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue