Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,10 @@ extension AccessibilityTests {
try await performAccessibilityAudit(named: "HomeScreenKnockedCell_Previews")
}

func testHomeScreenNewSoundBanner() async throws {
try await performAccessibilityAudit(named: "HomeScreenNewSoundBanner_Previews")
}

func testHomeScreenRecoveryKeyConfirmationBanner() async throws {
try await performAccessibilityAudit(named: "HomeScreenRecoveryKeyConfirmationBanner_Previews")
}
Expand Down
4 changes: 4 additions & 0 deletions ElementX.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
1621BF6316FFFEF5AE067C77 /* Avatars.swift in Sources */ = {isa = PBXBuildFile; fileRef = C142248014E08E885E323E56 /* Avatars.swift */; };
1653275750CE11F5CE94DDFD /* ReadReceiptsSummaryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8063E65441E771200108C558 /* ReadReceiptsSummaryView.swift */; };
167D00CAA13FAFB822298021 /* MediaProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62A81CCC2516D9CF9322DF01 /* MediaProviderTests.swift */; };
167D5024DB9D44197AEA0507 /* HomeScreenNewSoundBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD5480F03306234FC086E93B /* HomeScreenNewSoundBanner.swift */; };
16A1F6C703305FCAF4E14EC6 /* TimelineProxyMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A8AA0DFA06012A9DAB951E /* TimelineProxyMock.swift */; };
16A5D1749A32B91203495EF7 /* FrequentlyUsedEmoji.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A2074C0449B83D5858BD2D7 /* FrequentlyUsedEmoji.swift */; };
16CBD087038DE3815CDA512C /* PollMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = D38391154120264910D19528 /* PollMock.swift */; };
Expand Down Expand Up @@ -2474,6 +2475,7 @@
BC51BF90469412ABDE658CDD /* portrait_test_image.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = portrait_test_image.jpg; sourceTree = "<group>"; };
BC8AA23D4F37CC26564F63C5 /* LayoutMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutMocks.swift; sourceTree = "<group>"; };
BCDA016D05107DED3B9495CB /* TimelineItemDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemDebugView.swift; sourceTree = "<group>"; };
BD5480F03306234FC086E93B /* HomeScreenNewSoundBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenNewSoundBanner.swift; sourceTree = "<group>"; };
BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationContent.swift; sourceTree = "<group>"; };
BE89A8BD65CCE3FCC925CA14 /* TimelineItemReplyDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemReplyDetails.swift; sourceTree = "<group>"; };
BE98688578F8B0541D853695 /* test_pdf.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = test_pdf.pdf; sourceTree = "<group>"; };
Expand Down Expand Up @@ -4125,6 +4127,7 @@
C0FEA560929DD73FFEF8C3DF /* HomeScreenEmptyStateView.swift */,
D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */,
A103580EBA06155B1343EF16 /* HomeScreenKnockedCell.swift */,
BD5480F03306234FC086E93B /* HomeScreenNewSoundBanner.swift */,
05512FB13987D221B7205DE0 /* HomeScreenRecoveryKeyConfirmationBanner.swift */,
ED044D00F2176681CC02CD54 /* HomeScreenRoomCell.swift */,
C7661EFFCAA307A97D71132A /* HomeScreenRoomList.swift */,
Expand Down Expand Up @@ -7766,6 +7769,7 @@
22C5483D01EEB290B8339817 /* HomeScreenInviteCell.swift in Sources */,
86DFA58FBBEB0AF671D2A1E1 /* HomeScreenKnockedCell.swift in Sources */,
8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */,
167D5024DB9D44197AEA0507 /* HomeScreenNewSoundBanner.swift in Sources */,
B04E9EB589CE99C3929E817A /* HomeScreenRecoveryKeyConfirmationBanner.swift in Sources */,
0AE0AB1952F186EB86719B4F /* HomeScreenRoomCell.swift in Sources */,
A10D6CCDE2010C09EEA1A593 /* HomeScreenRoomList.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@
"banner_migrate_to_native_sliding_sync_description" = "Your server now supports a new, faster protocol. Log out and log back in to upgrade now. Doing this now will help you avoid a forced logout when the old protocol is removed later.";
"banner_migrate_to_native_sliding_sync_force_logout_title" = "Your homeserver no longer supports the old protocol. Please log out and log back in to continue using the app.";
"banner_migrate_to_native_sliding_sync_title" = "Upgrade available";
"banner_new_sound_message" = "Your notification ping has been updated—clearer, quicker, and less disruptive.";
"banner_new_sound_title" = "We’ve refreshed your sounds";
"banner_set_up_recovery_content" = "Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.";
"banner_set_up_recovery_title" = "Set up recovery to protect your account";
"call_invalid_audio_device_bluetooth_devices_disabled" = "Element Call does not support using Bluetooth audio devices in this Android version. Please select a different audio device.";
Expand Down
Binary file modified ElementX/Resources/Sounds/message.caf
Binary file not shown.
5 changes: 5 additions & 0 deletions ElementX/Sources/Application/AppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,11 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
await userSession.clientProxy.expireSyncSessions()
}

if oldVersion < Version(25, 10, 0) {
MXLog.info("Migrating to version 25.10.0, showing new sound banner to existing user.")
appSettings.hasSeenNewSoundBanner = false
}

userSessionMigrationsOldVersion = nil
}

Expand Down
5 changes: 5 additions & 0 deletions ElementX/Sources/Application/Settings/AppSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ final class AppSettings {
private enum UserDefaultsKeys: String {
case lastVersionLaunched
case seenInvites
case hasSeenNewSoundBanner
case appLockNumberOfPINAttempts
case appLockNumberOfBiometricAttempts
case timelineStyle
Expand Down Expand Up @@ -161,6 +162,10 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.seenInvites, defaultValue: [], storageType: .userDefaults(store))
var seenInvites: Set<String>

/// Defaults to `true` for new users, and we use a migration to set it to `false` for existing users.
@UserPreference(key: UserDefaultsKeys.hasSeenNewSoundBanner, defaultValue: true, storageType: .userDefaults(store))
var hasSeenNewSoundBanner

/// The initial set of account providers shown to the user in the authentication flow.
///
/// Account provider is the friendly term for the server name. It should not contain an `https` prefix and should
Expand Down
4 changes: 4 additions & 0 deletions ElementX/Sources/Generated/Strings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,10 @@ internal enum L10n {
internal static var bannerMigrateToNativeSlidingSyncForceLogoutTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_force_logout_title") }
/// Upgrade available
internal static var bannerMigrateToNativeSlidingSyncTitle: String { return L10n.tr("Localizable", "banner_migrate_to_native_sliding_sync_title") }
/// Your notification ping has been updated—clearer, quicker, and less disruptive.
internal static var bannerNewSoundMessage: String { return L10n.tr("Localizable", "banner_new_sound_message") }
/// We’ve refreshed your sounds
internal static var bannerNewSoundTitle: String { return L10n.tr("Localizable", "banner_new_sound_title") }
/// Recover your cryptographic identity and message history with a recovery key if you have lost all your existing devices.
internal static var bannerSetUpRecoveryContent: String { return L10n.tr("Localizable", "banner_set_up_recovery_content") }
/// Set up recovery
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ enum TestablePreviewsDictionary {
"HomeScreenEmptyStateView_Previews" : HomeScreenEmptyStateView_Previews.self,
"HomeScreenInviteCell_Previews" : HomeScreenInviteCell_Previews.self,
"HomeScreenKnockedCell_Previews" : HomeScreenKnockedCell_Previews.self,
"HomeScreenNewSoundBanner_Previews" : HomeScreenNewSoundBanner_Previews.self,
"HomeScreenRecoveryKeyConfirmationBanner_Previews" : HomeScreenRecoveryKeyConfirmationBanner_Previews.self,
"HomeScreenRoomCell_Previews" : HomeScreenRoomCell_Previews.self,
"HomeScreen_Previews" : HomeScreen_Previews.self,
Expand Down
6 changes: 6 additions & 0 deletions ElementX/Sources/Screens/HomeScreen/HomeScreenModels.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ enum HomeScreenViewAction {
case confirmRecoveryKey
case resetEncryption
case skipRecoveryKeyConfirmation
case dismissNewSoundBanner
case updateVisibleItemRange(Range<Int>)
case globalSearch
case markRoomAsUnread(roomIdentifier: String)
Expand Down Expand Up @@ -92,6 +93,7 @@ struct HomeScreenViewState: BindableState {
var userAvatarURL: URL?

var securityBannerMode = HomeScreenSecurityBannerMode.none
var shouldShowNewSoundBanner = false

var requiresExtraAccountSetup = false

Expand Down Expand Up @@ -134,6 +136,10 @@ struct HomeScreenViewState: BindableState {
var shouldShowFilters: Bool {
!bindings.isSearchFieldFocused && roomListMode == .rooms
}

var shouldShowBanner: Bool {
securityBannerMode.isShown || shouldShowNewSoundBanner
}
}

struct HomeScreenViewStateBindings {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
}
.store(in: &cancellables)

appSettings.$hasSeenNewSoundBanner
.sink { [weak self] hasSeenNewSoundBanner in
self?.state.shouldShowNewSoundBanner = !hasSeenNewSoundBanner
}
.store(in: &cancellables)

userSession.clientProxy.hideInviteAvatarsPublisher
.removeDuplicates()
.receive(on: DispatchQueue.main)
Expand Down Expand Up @@ -160,6 +166,8 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
actionsSubject.send(.presentEncryptionResetScreen)
case .skipRecoveryKeyConfirmation:
state.securityBannerMode = .dismissed
case .dismissNewSoundBanner:
appSettings.hasSeenNewSoundBanner = true
case .updateVisibleItemRange(let range):
roomSummaryProvider?.updateVisibleRange(range)
case .startChat:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,16 @@ struct HomeScreenContent: View {
@ViewBuilder
private var topSection: some View {
// An empty VStack causes glitches within the room list
if context.viewState.shouldShowFilters || context.viewState.securityBannerMode.isShown {
if context.viewState.shouldShowFilters || context.viewState.shouldShowBanner {
VStack(spacing: 0) {
if context.viewState.shouldShowFilters {
RoomListFiltersView(state: $context.filtersState)
}

if case let .show(state) = context.viewState.securityBannerMode {
HomeScreenRecoveryKeyConfirmationBanner(state: state, context: context)
} else if context.viewState.shouldShowNewSoundBanner {
HomeScreenNewSoundBanner { context.send(viewAction: .dismissNewSoundBanner) }
}
}
.background(Color.compound.bgCanvasDefault)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// 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.
//

import Compound
import SwiftUI

struct HomeScreenNewSoundBanner: View {
let dismissAction: () -> Void

var body: some View {
VStack(spacing: 16) {
content
buttons
}
.padding(16)
.background(Color.compound.bgSubtleSecondary)
.cornerRadius(14)
.padding(.horizontal, 16)
}

var content: some View {
VStack(alignment: .leading, spacing: 4) {
HStack(alignment: .firstTextBaseline, spacing: 16) {
Text(L10n.bannerNewSoundTitle)
.font(.compound.bodyLGSemibold)
.foregroundColor(.compound.textPrimary)
.frame(maxWidth: .infinity, alignment: .leading)

Button(action: dismissAction) {
Image(systemName: "xmark")
.foregroundColor(.compound.iconSecondary)
.frame(width: 12, height: 12)
}
}

Text(L10n.bannerNewSoundMessage)
.font(.compound.bodyMD)
.foregroundColor(.compound.textSecondary)
}
}

var buttons: some View {
Button(action: dismissAction) {
Text(L10n.actionOk)
.frame(maxWidth: .infinity)
}
.buttonStyle(.compound(.primary, size: .medium))
}
}

struct HomeScreenNewSoundBanner_Previews: PreviewProvider, TestablePreview {
static var previews: some View {
HomeScreenNewSoundBanner { }
}
}
6 changes: 6 additions & 0 deletions PreviewTests/Sources/GeneratedPreviewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,12 @@ extension PreviewTests {
}
}

func testHomeScreenNewSoundBanner() async throws {
for (index, preview) in HomeScreenNewSoundBanner_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
}
}

func testHomeScreenRecoveryKeyConfirmationBanner() async throws {
for (index, preview) in HomeScreenRecoveryKeyConfirmationBanner_Previews._allPreviews.enumerated() {
try await assertSnapshots(matching: preview, step: index)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions UnitTests/Sources/HomeScreenViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,19 @@ class HomeScreenViewModelTests: XCTestCase {
try await deferredAction.fulfill()
}

func testNewSoundBanner() {
appSettings.hasSeenNewSoundBanner = false

setupViewModel()
XCTAssertTrue(context.viewState.shouldShowBanner)
XCTAssertTrue(context.viewState.shouldShowNewSoundBanner)

context.send(viewAction: .dismissNewSoundBanner)
XCTAssertFalse(context.viewState.shouldShowBanner)
XCTAssertFalse(context.viewState.shouldShowNewSoundBanner)
XCTAssertTrue(appSettings.hasSeenNewSoundBanner)
}

// MARK: - Helpers

enum InviteType { case rooms, spaces }
Expand Down
Loading