First Commit
This commit is contained in:
14
features/announcement/api/build.gradle.kts
Normal file
14
features/announcement/api/build.gradle.kts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,14 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,
|
||||
NewNotificationSound,
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AnnouncementService {
|
||||
suspend fun showAnnouncement(announcement: Announcement)
|
||||
|
||||
suspend fun onAnnouncementDismissed(announcement: Announcement)
|
||||
|
||||
fun announcementsToShowFlow(): Flow<List<Announcement>>
|
||||
|
||||
/**
|
||||
* Use this composable to render the announcement UI in Fullscreen.
|
||||
*/
|
||||
@Composable
|
||||
fun Render(
|
||||
modifier: Modifier,
|
||||
)
|
||||
}
|
||||
38
features/announcement/impl/build.gradle.kts
Normal file
38
features/announcement/impl/build.gradle.kts
Normal file
@@ -0,0 +1,38 @@
|
||||
import extension.setupDependencyInjection
|
||||
import extension.testCommonDependencies
|
||||
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,37 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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.api.Announcement
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||
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.announcementStatusFlow(Announcement.Space).map {
|
||||
it == AnnouncementStatus.Show
|
||||
}
|
||||
}.collectAsState(false)
|
||||
return AnnouncementState(
|
||||
showSpaceAnnouncement = showSpaceAnnouncement,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,89 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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 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.AnnouncementStatus
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.first
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
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()
|
||||
Announcement.NewNotificationSound -> {
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun onAnnouncementDismissed(announcement: Announcement) {
|
||||
announcementStore.setAnnouncementStatus(announcement, AnnouncementStatus.Shown)
|
||||
}
|
||||
|
||||
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
||||
return combine(
|
||||
announcementStore.announcementStatusFlow(Announcement.Space),
|
||||
announcementStore.announcementStatusFlow(Announcement.NewNotificationSound),
|
||||
) { spaceAnnouncementStatus, newNotificationSoundStatus ->
|
||||
buildList {
|
||||
if (spaceAnnouncementStatus == AnnouncementStatus.Show) {
|
||||
add(Announcement.Space)
|
||||
}
|
||||
if (newNotificationSoundStatus == AnnouncementStatus.Show) {
|
||||
add(Announcement.NewNotificationSound)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun showSpaceAnnouncement() {
|
||||
val currentValue = announcementStore.announcementStatusFlow(Announcement.Space).first()
|
||||
if (currentValue == AnnouncementStatus.NeverShown) {
|
||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.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,29 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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.api.Announcement
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStore
|
||||
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 handleEvent(event: SpaceAnnouncementEvents) {
|
||||
when (event) {
|
||||
SpaceAnnouncementEvents.Continue -> localCoroutineScope.launch {
|
||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SpaceAnnouncementState(
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,24 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,158 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,15 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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
|
||||
|
||||
enum class AnnouncementStatus {
|
||||
NeverShown,
|
||||
Show,
|
||||
Shown,
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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 io.element.android.features.announcement.api.Announcement
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface AnnouncementStore {
|
||||
suspend fun setAnnouncementStatus(
|
||||
announcement: Announcement,
|
||||
status: AnnouncementStatus,
|
||||
)
|
||||
|
||||
fun announcementStatusFlow(
|
||||
announcement: Announcement,
|
||||
): Flow<AnnouncementStatus>
|
||||
|
||||
suspend fun reset()
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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 io.element.android.features.announcement.api.Announcement
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
||||
private val newNotificationSoundKey = intPreferencesKey("newNotificationSound")
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAnnouncementStore(
|
||||
preferenceDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : AnnouncementStore {
|
||||
private val store = preferenceDataStoreFactory.create("elementx_announcement")
|
||||
|
||||
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||
val key = announcement.toKey()
|
||||
store.edit { prefs ->
|
||||
prefs[key] = status.ordinal
|
||||
}
|
||||
}
|
||||
|
||||
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||
val key = announcement.toKey()
|
||||
// For NewNotificationSound, a migration will set it to Show on application upgrade (see AppMigration08)
|
||||
val defaultStatus = when (announcement) {
|
||||
Announcement.Space -> AnnouncementStatus.NeverShown
|
||||
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
||||
}
|
||||
return store.data.map { prefs ->
|
||||
val ordinal = prefs[key] ?: defaultStatus.ordinal
|
||||
AnnouncementStatus.entries.getOrElse(ordinal) { defaultStatus }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
store.edit { it.clear() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun Announcement.toKey() = when (this) {
|
||||
Announcement.Space -> spaceAnnouncementKey
|
||||
Announcement.NewNotificationSound -> newNotificationSoundKey
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="screen_space_announcement_item4">"Присъединете се към обществени пространства"</string>
|
||||
</resources>
|
||||
@@ -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">"Zobrazit prostory, které jste vytvořili nebo ke kterým jste se připojili"</string>
|
||||
<string name="screen_space_announcement_item2">"Přijmout nebo odmítnout pozvánky do prostorů"</string>
|
||||
<string name="screen_space_announcement_item3">"Objevte všechny místnosti, do kterých můžete vstoupit ve svých prostorech"</string>
|
||||
<string name="screen_space_announcement_item4">"Připojit se k veřejným prostorům"</string>
|
||||
<string name="screen_space_announcement_item5">"Opustit všechny prostory, ke kterým jste se připojili"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrování, vytváření a správa prostorů bude brzy k dispozici."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Vítejte v beta verzi prostorů! S touto první verzí můžete:"</string>
|
||||
<string name="screen_space_announcement_title">"Představujeme prostory"</string>
|
||||
</resources>
|
||||
@@ -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">"Se grupper, du har oprettet eller tilmeldt dig"</string>
|
||||
<string name="screen_space_announcement_item2">"Acceptere eller afvise invitationer til grupper"</string>
|
||||
<string name="screen_space_announcement_item3">"Finde alle rum, du kan deltage i, i dine grupper"</string>
|
||||
<string name="screen_space_announcement_item4">"Deltage i offentlige grupper"</string>
|
||||
<string name="screen_space_announcement_item5">"Forlade de grupper, du har tilsluttet dig"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrering, oprettelse og administration af grupper kommer snart."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Velkommen til betaversionen af Grupper! Med denne første version kan du:"</string>
|
||||
<string name="screen_space_announcement_title">"Introduktion til Grupper"</string>
|
||||
</resources>
|
||||
@@ -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">"Von dir erstellte oder beigetretene Spaces anzeigen"</string>
|
||||
<string name="screen_space_announcement_item2">"Einladungen zu Spaces annehmen oder ablehnen"</string>
|
||||
<string name="screen_space_announcement_item3">"Chats innerhalb deiner Spaces entdecken, um ihnen beizutreten"</string>
|
||||
<string name="screen_space_announcement_item4">"Öffentlichen Spaces beitreten"</string>
|
||||
<string name="screen_space_announcement_item5">"Spaces verlassen, bei denen du Mitglied bist"</string>
|
||||
<string name="screen_space_announcement_notice">"Das Filtern, Erstellen und Verwalten von Spaces ist bald verfügbar."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Willkommen bei der Beta-Version von Spaces! Mit dieser ersten Version kannst du:"</string>
|
||||
<string name="screen_space_announcement_title">"Einführung in Spaces"</string>
|
||||
</resources>
|
||||
@@ -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">"Vaadata kogukondi, mille oled loonud või millega oled liitunud"</string>
|
||||
<string name="screen_space_announcement_item2">"Nõustuda kutsetega liitumiseks kogukonnaga või sellest keelduda"</string>
|
||||
<string name="screen_space_announcement_item3">"Uurida neis kogukondades leiduvaid jututube ning nendega liituda"</string>
|
||||
<string name="screen_space_announcement_item4">"Liituda avalike kogukondadega"</string>
|
||||
<string name="screen_space_announcement_item5">"Lahkuda kogukonnast, millega oled liitunud"</string>
|
||||
<string name="screen_space_announcement_notice">"Kogukondade filtreerimine, loomine ja haldamine lisandub peagi"</string>
|
||||
<string name="screen_space_announcement_subtitle">"Tere tulemast kasutama kogukondade beetaversiooni! Selles esimeses versioonis saad sa:"</string>
|
||||
<string name="screen_space_announcement_title">"Võtame kasutusele kogukonnad"</string>
|
||||
</resources>
|
||||
@@ -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">"دیدن فضاهایی که ساخته یا پیوستهاید"</string>
|
||||
<string name="screen_space_announcement_item2">"پذیرش یا رد دعوتها به فضاها"</string>
|
||||
<string name="screen_space_announcement_item3">"کشف تمامی اتاقهایی که میتوانید در فضاهایتان بپیوندید"</string>
|
||||
<string name="screen_space_announcement_item4">"پیوستن به فضاهای عمومی"</string>
|
||||
<string name="screen_space_announcement_item5">"ترک هر فضایی که پیوستهاید"</string>
|
||||
<string name="screen_space_announcement_notice">"پالایش، ایجاد و مدیریت کردن فضاها به زودی."</string>
|
||||
<string name="screen_space_announcement_subtitle">"به نگارش آزمایشی فضاها خوش آمدید! در این نگارش میتوانید:"</string>
|
||||
<string name="screen_space_announcement_title">"معرّفی فضاها"</string>
|
||||
</resources>
|
||||
@@ -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">"Nähdä luomasi tai liittymäsi tilat"</string>
|
||||
<string name="screen_space_announcement_item2">"Hyväksyä tai hylätä kutsuja tiloihin"</string>
|
||||
<string name="screen_space_announcement_item3">"Löytää kaikki huoneet, joihin voit liittyä tiloissasi"</string>
|
||||
<string name="screen_space_announcement_item4">"Liittyä julkisiin tiloihin"</string>
|
||||
<string name="screen_space_announcement_item5">"Poistua mistä tahansa tilasta, johon olet liittynyt"</string>
|
||||
<string name="screen_space_announcement_notice">"Tilojen suodatus, luominen ja hallinta on tulossa pian."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Tervetuloa tilojen beetaversioon! Tämän ensimmäisen version avulla voit:"</string>
|
||||
<string name="screen_space_announcement_title">"Esittelyssä tilat"</string>
|
||||
</resources>
|
||||
@@ -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">"Voir les espaces que vous avez créés ou rejoints"</string>
|
||||
<string name="screen_space_announcement_item2">"Accepter ou refuser les invitations aux espaces"</string>
|
||||
<string name="screen_space_announcement_item3">"Découvrir les salons que vous pouvez joindre depuis vos espaces"</string>
|
||||
<string name="screen_space_announcement_item4">"Rejoindre les espaces publics"</string>
|
||||
<string name="screen_space_announcement_item5">"Quitter les espaces dont vous êtes membre."</string>
|
||||
<string name="screen_space_announcement_notice">"Le filtrage, la création et la gestion des espaces seront bientôt disponibles."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Bienvenue dans la version bêta des espaces! Avec cette première version, vous pourrez :"</string>
|
||||
<string name="screen_space_announcement_title">"Ajout des espaces"</string>
|
||||
</resources>
|
||||
@@ -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">"Az Ön által létrehozott vagy csatlakozott térek megtekintése"</string>
|
||||
<string name="screen_space_announcement_item2">"A meghívások elfogadására vagy elutasítására a terekhez"</string>
|
||||
<string name="screen_space_announcement_item3">"Szobák felfedezése a terekben, amelyekhez csatlakozhat"</string>
|
||||
<string name="screen_space_announcement_item4">"Csatlakozás nyilvános terekhez"</string>
|
||||
<string name="screen_space_announcement_item5">"Terek elhagyása"</string>
|
||||
<string name="screen_space_announcement_notice">"Terek szűrése, készítése és kezelése hamarosan érkezik."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Üdvözöljük a tér béta verziójában! Ezzel az első verzióval a következőket teheti:"</string>
|
||||
<string name="screen_space_announcement_title">"Bemutatkoznak a terek"</string>
|
||||
</resources>
|
||||
@@ -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">"Visualizza gli spazi che hai creato o a cui partecipi"</string>
|
||||
<string name="screen_space_announcement_item2">"Accetta o rifiuta gli inviti agli spazi"</string>
|
||||
<string name="screen_space_announcement_item3">"Scopri tutte le stanze a cui puoi partecipare nei tuoi spazi"</string>
|
||||
<string name="screen_space_announcement_item4">"Unisciti agli spazi pubblici"</string>
|
||||
<string name="screen_space_announcement_item5">"Lascia tutti gli spazi a cui ti sei unito"</string>
|
||||
<string name="screen_space_announcement_notice">"A breve saranno disponibili le funzionalità di filtraggio, creazione e gestione degli spazi."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Benvenuti alla versione beta degli Spazi! Con questa prima versione potrete:"</string>
|
||||
<string name="screen_space_announcement_title">"Ti presentiamo gli Spazi"</string>
|
||||
</resources>
|
||||
@@ -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">"Se områder du har opprettet eller blitt med i"</string>
|
||||
<string name="screen_space_announcement_item2">"Godta eller avslå invitasjoner til områder"</string>
|
||||
<string name="screen_space_announcement_item3">"Oppdag alle rom du kan bli med i i dine områder"</string>
|
||||
<string name="screen_space_announcement_item4">"Bli med i offentlige områder"</string>
|
||||
<string name="screen_space_announcement_item5">"Forlat områder du har blitt med i"</string>
|
||||
<string name="screen_space_announcement_notice">"Oppretting, filtrering og administrasjon av områder kommer snart."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Velkommen til betaversjonen av Områder! Med denne første versjonen kan du:"</string>
|
||||
<string name="screen_space_announcement_title">"Vi introduserer Områder"</string>
|
||||
</resources>
|
||||
@@ -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">"Wyświetlić przestrzenie, które stworzyłeś lub do których dołączyłeś"</string>
|
||||
<string name="screen_space_announcement_item2">"Akceptować lub odrzucać zaproszenia"</string>
|
||||
<string name="screen_space_announcement_item3">"Odkrywać wszystkie pokoje, do których możesz dołączyć w swoich przestrzeniach"</string>
|
||||
<string name="screen_space_announcement_item4">"Dołączać do przestrzeni publicznych"</string>
|
||||
<string name="screen_space_announcement_item5">"Opuszczać jakąkolwiek przestrzeń, do której dołączyłeś"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrowanie, tworzenie i zarządzanie przestrzeniami pojawi się wkrótce."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Witamy w wersji beta przestrzeni! W tej wersji możesz:"</string>
|
||||
<string name="screen_space_announcement_title">"Przedstawiamy przestrzenie"</string>
|
||||
</resources>
|
||||
@@ -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">"Visualizar espaços que criou ou entrou"</string>
|
||||
<string name="screen_space_announcement_item2">"Aceitar ou recusar convites aos espaços"</string>
|
||||
<string name="screen_space_announcement_item3">"Descobrir quaisquer salas que você pode entrar nos espaços"</string>
|
||||
<string name="screen_space_announcement_item4">"Entrar espaços públicos"</string>
|
||||
<string name="screen_space_announcement_item5">"Sair de quaisquer espaços que entrou"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrar, criar, e gerenciar espaços virão em breve."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Boas-vindas à versão beta dos Espaços! Com essa primeira versão, você pode:"</string>
|
||||
<string name="screen_space_announcement_title">"Apresentando Espaços"</string>
|
||||
</resources>
|
||||
@@ -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">"Vizualizați spațiile pe care le-ați creat sau la care v-ați alăturat"</string>
|
||||
<string name="screen_space_announcement_item2">"Acceptați sau refuzați invitațiile la spații"</string>
|
||||
<string name="screen_space_announcement_item3">"Descoperiți toate camerele la care vă puteți alătura în spațiile dumneavoastră."</string>
|
||||
<string name="screen_space_announcement_item4">"Alăturați-vă spațiilor publice"</string>
|
||||
<string name="screen_space_announcement_item5">"Părăsiți spațiile la care v-ați alăturat."</string>
|
||||
<string name="screen_space_announcement_notice">"Crearea și gestionarea spațiilor vor fi disponibile în curând."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Bun venit la versiunea beta a Spațiilor! Cu această primă versiune puteți:"</string>
|
||||
<string name="screen_space_announcement_title">"Vă prezentăm Spații"</string>
|
||||
</resources>
|
||||
@@ -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">"Просмотр пространств, которые вы создали или к которым присоединились"</string>
|
||||
<string name="screen_space_announcement_item2">"Принимать или отклонять приглашения в пространства"</string>
|
||||
<string name="screen_space_announcement_item3">"Откройте для себя все комнаты, к которым вы можете присоединиться в своих пространствах."</string>
|
||||
<string name="screen_space_announcement_item4">"Присоединиться к публичному пространству"</string>
|
||||
<string name="screen_space_announcement_item5">"Покинуть все пространства, к которым вы присоединились"</string>
|
||||
<string name="screen_space_announcement_notice">"Работа с пространствами скоро станет доступна"</string>
|
||||
<string name="screen_space_announcement_subtitle">"Добро пожаловать в бета-версию Spaces! В этой первой версии вы сможете:"</string>
|
||||
<string name="screen_space_announcement_title">"Представляем пространства"</string>
|
||||
</resources>
|
||||
@@ -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">"Zobraziť priestory, ktoré ste vytvorili alebo ku ktorým ste sa pripojili"</string>
|
||||
<string name="screen_space_announcement_item2">"Prijímať alebo odmietať pozvánky do priestorov"</string>
|
||||
<string name="screen_space_announcement_item3">"Objaviť všetky miestnosti, do ktorých sa môžete pripojiť vo svojich priestoroch"</string>
|
||||
<string name="screen_space_announcement_item4">"Pripojiť sa k verejnému priestoru"</string>
|
||||
<string name="screen_space_announcement_item5">"Opustiť akékoľvek priestory, ku ktorým ste sa pridali"</string>
|
||||
<string name="screen_space_announcement_notice">"Filtrovanie, vytváranie a správa priestorov bude čoskoro k dispozícii."</string>
|
||||
<string name="screen_space_announcement_subtitle">"Vitajte v beta verzii priestorov! S touto prvou verziou môžete:"</string>
|
||||
<string name="screen_space_announcement_title">"Predstavujeme priestory"</string>
|
||||
</resources>
|
||||
@@ -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">"檢視您建立或加入的空間"</string>
|
||||
<string name="screen_space_announcement_item2">"接受或拒絕空間邀請"</string>
|
||||
<string name="screen_space_announcement_item3">"探索空間內您可以加入的任何聊天室"</string>
|
||||
<string name="screen_space_announcement_item4">"加入公開空間"</string>
|
||||
<string name="screen_space_announcement_item5">"離開任何您已加入的空間"</string>
|
||||
<string name="screen_space_announcement_notice">"篩選、建立與管理空間功能即將推出。"</string>
|
||||
<string name="screen_space_announcement_subtitle">"歡迎使用空間的測試版!此初始版本可讓您:"</string>
|
||||
<string name="screen_space_announcement_title">"介紹空間"</string>
|
||||
</resources>
|
||||
@@ -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">"查看您创建或加入的空间"</string>
|
||||
<string name="screen_space_announcement_item2">"接受或拒绝空间邀请"</string>
|
||||
<string name="screen_space_announcement_item3">"发现您可以加入空间的所有房间"</string>
|
||||
<string name="screen_space_announcement_item4">"加入公共空间"</string>
|
||||
<string name="screen_space_announcement_item5">"离开你加入的所有空间"</string>
|
||||
<string name="screen_space_announcement_notice">"筛选、创建及管理空间功能即将上线。"</string>
|
||||
<string name="screen_space_announcement_subtitle">"欢迎使用 Spaces 测试版!使用首个版本,您可以:"</string>
|
||||
<string name="screen_space_announcement_title">"Spaces 简介"</string>
|
||||
</resources>
|
||||
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">"Filtering, 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,53 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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.store.AnnouncementStatus
|
||||
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.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
||||
val updatedState = awaitItem()
|
||||
assertThat(updatedState.showSpaceAnnouncement).isTrue()
|
||||
store.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showSpaceAnnouncement).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAnnouncementPresenter(
|
||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||
) = AnnouncementPresenter(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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 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.impl.spaces.SpaceAnnouncementState
|
||||
import io.element.android.features.announcement.impl.spaces.aSpaceAnnouncementState
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||
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.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||
sut.showAnnouncement(Announcement.Space)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Show)
|
||||
// Simulate user close the announcement
|
||||
sut.onAnnouncementDismissed(Announcement.Space)
|
||||
// Entering again the space tab should not change the value
|
||||
sut.showAnnouncement(Announcement.Space)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when showing NewNotificationSound announcement, announcement is set to show even if it was already shown`() = runTest {
|
||||
val announcementStore = InMemoryAnnouncementStore()
|
||||
val sut = createDefaultAnnouncementService(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||
sut.showAnnouncement(Announcement.NewNotificationSound)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.Show)
|
||||
// Simulate user close the announcement
|
||||
sut.onAnnouncementDismissed(Announcement.NewNotificationSound)
|
||||
// Calling again showAnnouncement should set it back to Show
|
||||
sut.showAnnouncement(Announcement.NewNotificationSound)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.NewNotificationSound).first()).isEqualTo(AnnouncementStatus.Show)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test announcementsToShowFlow`() = runTest {
|
||||
val announcementStore = InMemoryAnnouncementStore()
|
||||
val sut = createDefaultAnnouncementService(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
sut.announcementsToShowFlow().test {
|
||||
assertThat(awaitItem()).isEmpty()
|
||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Show)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.Space)
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.Space, Announcement.NewNotificationSound)
|
||||
announcementStore.setAnnouncementStatus(Announcement.Space, AnnouncementStatus.Shown)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound)
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Shown)
|
||||
assertThat(awaitItem()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
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,41 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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.api.Announcement
|
||||
import io.element.android.features.announcement.impl.store.AnnouncementStatus
|
||||
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.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||
val state = awaitItem()
|
||||
state.eventSink(SpaceAnnouncementEvents.Continue)
|
||||
assertThat(store.announcementStatusFlow(Announcement.Space).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSpaceAnnouncementPresenter(
|
||||
announcementStore: AnnouncementStore = InMemoryAnnouncementStore(),
|
||||
) = SpaceAnnouncementPresenter(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,40 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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 io.element.android.features.announcement.api.Announcement
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class InMemoryAnnouncementStore(
|
||||
initialSpaceAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
initialNewNotificationSoundAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
) : AnnouncementStore {
|
||||
private val spaceAnnouncement = MutableStateFlow(initialSpaceAnnouncementStatus)
|
||||
private val newNotificationSoundAnnouncement = MutableStateFlow(initialNewNotificationSoundAnnouncementStatus)
|
||||
|
||||
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||
announcement.toMutableStateFlow().value = status
|
||||
}
|
||||
|
||||
override fun announcementStatusFlow(announcement: Announcement): Flow<AnnouncementStatus> {
|
||||
return announcement.toMutableStateFlow().asStateFlow()
|
||||
}
|
||||
|
||||
override suspend fun reset() {
|
||||
spaceAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
newNotificationSoundAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
}
|
||||
|
||||
private fun Announcement.toMutableStateFlow() = when (this) {
|
||||
Announcement.Space -> spaceAnnouncement
|
||||
Announcement.NewNotificationSound -> newNotificationSoundAnnouncement
|
||||
}
|
||||
}
|
||||
20
features/announcement/test/build.gradle.kts
Normal file
20
features/announcement/test/build.gradle.kts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* 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,48 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Element Creations Ltd.
|
||||
* Copyright 2024, 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.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
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
|
||||
class FakeAnnouncementService(
|
||||
initialAnnouncementsToShowFlowValue: List<Announcement> = emptyList(),
|
||||
val showAnnouncementResult: (Announcement) -> Unit = { lambdaError() },
|
||||
val onAnnouncementDismissedResult: (Announcement) -> Unit = { lambdaError() },
|
||||
val renderResult: (Modifier) -> Unit = { lambdaError() },
|
||||
) : AnnouncementService {
|
||||
private val announcementsToShowFlowValue = MutableStateFlow(initialAnnouncementsToShowFlowValue)
|
||||
|
||||
override suspend fun showAnnouncement(announcement: Announcement) {
|
||||
showAnnouncementResult(announcement)
|
||||
}
|
||||
|
||||
override suspend fun onAnnouncementDismissed(announcement: Announcement) {
|
||||
onAnnouncementDismissedResult(announcement)
|
||||
}
|
||||
|
||||
override fun announcementsToShowFlow(): Flow<List<Announcement>> {
|
||||
return announcementsToShowFlowValue.asStateFlow()
|
||||
}
|
||||
|
||||
fun emitAnnouncementsToShow(value: List<Announcement>) {
|
||||
announcementsToShowFlowValue.value = value
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Render(modifier: Modifier) {
|
||||
renderResult(modifier)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user