mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 20:01:55 +00:00
Announce the swipe gesture on the account avatar
Display a banner on the room list when 2 or more accounts are logged in, to let the user know that they can swipe their avatar to switch account. The banner is stored as a new Announcement.SwipeAvatarToSwitchAccount, and is dismissed when the user taps on it, or when the user switches account by swiping their avatar. The banner is displayed at the same place as the new notification sound banner, but never at the same time. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+2
@@ -19,4 +19,6 @@ sealed interface Announcement {
|
||||
data object NewNotificationSound : Announcement
|
||||
|
||||
data object MultiAccount : Announcement
|
||||
|
||||
data object SwipeAvatarToSwitchAccount : Announcement
|
||||
}
|
||||
|
||||
+7
-1
@@ -34,6 +34,7 @@ class DefaultAnnouncementService(
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||
}
|
||||
Announcement.MultiAccount -> showAnnouncementIfNeverShown(announcement)
|
||||
Announcement.SwipeAvatarToSwitchAccount -> showAnnouncementIfNeverShown(announcement)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +47,8 @@ class DefaultAnnouncementService(
|
||||
flowOf(Unit),
|
||||
announcementStore.announcementStatusFlow(Announcement.NewNotificationSound),
|
||||
announcementStore.announcementStatusFlow(Announcement.MultiAccount),
|
||||
) { _, newNotificationSoundStatus, multiAccountStatus ->
|
||||
announcementStore.announcementStatusFlow(Announcement.SwipeAvatarToSwitchAccount),
|
||||
) { _, newNotificationSoundStatus, multiAccountStatus, swipeAvatarToSwitchAccountStatus ->
|
||||
buildList {
|
||||
if (newNotificationSoundStatus == AnnouncementStatus.Show) {
|
||||
add(Announcement.NewNotificationSound)
|
||||
@@ -56,6 +58,10 @@ class DefaultAnnouncementService(
|
||||
if (multiAccountStatus != AnnouncementStatus.Shown) {
|
||||
add(Announcement.MultiAccount)
|
||||
}
|
||||
// The swipe avatar announcement is pending until it is dismissed, or the user switches account by swiping.
|
||||
if (swipeAvatarToSwitchAccountStatus != AnnouncementStatus.Shown) {
|
||||
add(Announcement.SwipeAvatarToSwitchAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.map
|
||||
private val spaceAnnouncementKey = intPreferencesKey("spaceAnnouncement")
|
||||
private val newNotificationSoundKey = intPreferencesKey("newNotificationSound")
|
||||
private val multiAccountKey = intPreferencesKey("multiAccountAnnouncement")
|
||||
private val swipeAvatarToSwitchAccountKey = intPreferencesKey("swipeAvatarToSwitchAccountAnnouncement")
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAnnouncementStore(
|
||||
@@ -42,10 +43,13 @@ class DefaultAnnouncementStore(
|
||||
// - NewNotificationSound is opted in by AppMigration08
|
||||
// - MultiAccount is NeverShown by default, which means pending. It is set to Shown when the user enters
|
||||
// the login flow while the feature is enabled (see NotLoggedInFlowNode), or dismisses the announcement.
|
||||
// - SwipeAvatarToSwitchAccount is NeverShown by default, which means pending. It is set to Shown when the user
|
||||
// dismisses the banner, or switches account by swiping their avatar.
|
||||
val defaultStatus = when (announcement) {
|
||||
Announcement.Fullscreen.Space -> AnnouncementStatus.Shown
|
||||
Announcement.NewNotificationSound -> AnnouncementStatus.Shown
|
||||
Announcement.MultiAccount -> AnnouncementStatus.NeverShown
|
||||
Announcement.SwipeAvatarToSwitchAccount -> AnnouncementStatus.NeverShown
|
||||
}
|
||||
return store.data.map { prefs ->
|
||||
val ordinal = prefs[key] ?: defaultStatus.ordinal
|
||||
@@ -62,4 +66,5 @@ private fun Announcement.toKey() = when (this) {
|
||||
Announcement.Fullscreen.Space -> spaceAnnouncementKey
|
||||
Announcement.NewNotificationSound -> newNotificationSoundKey
|
||||
Announcement.MultiAccount -> multiAccountKey
|
||||
Announcement.SwipeAvatarToSwitchAccount -> swipeAvatarToSwitchAccountKey
|
||||
}
|
||||
|
||||
+23
-5
@@ -67,6 +67,22 @@ class DefaultAnnouncementServiceTest {
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.MultiAccount).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when showing SwipeAvatarToSwitchAccount announcement, announcement is set to show only if it was never shown`() = runTest {
|
||||
val announcementStore = InMemoryAnnouncementStore()
|
||||
val sut = createDefaultAnnouncementService(
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.SwipeAvatarToSwitchAccount).first()).isEqualTo(AnnouncementStatus.NeverShown)
|
||||
sut.showAnnouncement(Announcement.SwipeAvatarToSwitchAccount)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.SwipeAvatarToSwitchAccount).first()).isEqualTo(AnnouncementStatus.Show)
|
||||
// Simulate user close the announcement
|
||||
sut.onAnnouncementDismissed(Announcement.SwipeAvatarToSwitchAccount)
|
||||
// Calling again showAnnouncement should not change the value
|
||||
sut.showAnnouncement(Announcement.SwipeAvatarToSwitchAccount)
|
||||
assertThat(announcementStore.announcementStatusFlow(Announcement.SwipeAvatarToSwitchAccount).first()).isEqualTo(AnnouncementStatus.Shown)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test announcementsToShowFlow`() = runTest {
|
||||
val announcementStore = InMemoryAnnouncementStore()
|
||||
@@ -74,16 +90,18 @@ class DefaultAnnouncementServiceTest {
|
||||
announcementStore = announcementStore,
|
||||
)
|
||||
sut.announcementsToShowFlow().test {
|
||||
// MultiAccount is pending by default
|
||||
assertThat(awaitItem()).containsExactly(Announcement.MultiAccount)
|
||||
// MultiAccount and SwipeAvatarToSwitchAccount are pending by default
|
||||
assertThat(awaitItem()).containsExactly(Announcement.MultiAccount, Announcement.SwipeAvatarToSwitchAccount)
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Show)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound, Announcement.MultiAccount)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound, Announcement.MultiAccount, Announcement.SwipeAvatarToSwitchAccount)
|
||||
announcementStore.setAnnouncementStatus(Announcement.MultiAccount, AnnouncementStatus.Show)
|
||||
// Still pending
|
||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound, Announcement.MultiAccount)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.NewNotificationSound, Announcement.MultiAccount, Announcement.SwipeAvatarToSwitchAccount)
|
||||
announcementStore.setAnnouncementStatus(Announcement.NewNotificationSound, AnnouncementStatus.Shown)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.MultiAccount)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.MultiAccount, Announcement.SwipeAvatarToSwitchAccount)
|
||||
announcementStore.setAnnouncementStatus(Announcement.MultiAccount, AnnouncementStatus.Shown)
|
||||
assertThat(awaitItem()).containsExactly(Announcement.SwipeAvatarToSwitchAccount)
|
||||
announcementStore.setAnnouncementStatus(Announcement.SwipeAvatarToSwitchAccount, AnnouncementStatus.Shown)
|
||||
assertThat(awaitItem()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -17,10 +17,12 @@ class InMemoryAnnouncementStore(
|
||||
initialFullscreenAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
initialNewNotificationSoundAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
initialMultiAccountAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
initialSwipeAvatarToSwitchAccountAnnouncementStatus: AnnouncementStatus = AnnouncementStatus.NeverShown,
|
||||
) : AnnouncementStore {
|
||||
private val fullScreenAnnouncement = MutableStateFlow(initialFullscreenAnnouncementStatus)
|
||||
private val newNotificationSoundAnnouncement = MutableStateFlow(initialNewNotificationSoundAnnouncementStatus)
|
||||
private val multiAccountAnnouncement = MutableStateFlow(initialMultiAccountAnnouncementStatus)
|
||||
private val swipeAvatarToSwitchAccountAnnouncement = MutableStateFlow(initialSwipeAvatarToSwitchAccountAnnouncementStatus)
|
||||
|
||||
override suspend fun setAnnouncementStatus(announcement: Announcement, status: AnnouncementStatus) {
|
||||
announcement.toMutableStateFlow().value = status
|
||||
@@ -34,11 +36,13 @@ class InMemoryAnnouncementStore(
|
||||
fullScreenAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
newNotificationSoundAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
multiAccountAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
swipeAvatarToSwitchAccountAnnouncement.value = AnnouncementStatus.NeverShown
|
||||
}
|
||||
|
||||
private fun Announcement.toMutableStateFlow() = when (this) {
|
||||
is Announcement.Fullscreen -> fullScreenAnnouncement
|
||||
Announcement.NewNotificationSound -> newNotificationSoundAnnouncement
|
||||
Announcement.MultiAccount -> multiAccountAnnouncement
|
||||
Announcement.SwipeAvatarToSwitchAccount -> swipeAvatarToSwitchAccountAnnouncement
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user