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 @@ -615,6 +615,10 @@ extension AccessibilityTests {
try await performAccessibilityAudit(named: "SpaceScreen_Previews")
}

func testSpacesAnnouncementSheetView() async throws {
try await performAccessibilityAudit(named: "SpacesAnnouncementSheetView_Previews")
}

func testSplashScreen() async throws {
try await performAccessibilityAudit(named: "SplashScreen_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 @@ -881,6 +881,7 @@
9D2E03DB175A6AB14589076D /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = 2A3F7BCCB18C15B30CCA39A9 /* AnalyticsEvents */; };
9D79B94493FB32249F7E472F /* PlaceholderAvatarImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */; };
9D9690D2FD4CD26FF670620F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C75EF87651B00A176AB08E97 /* AppDelegate.swift */; };
9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7DA8FFD31B18324CC04A823 /* SpacesAnnouncementSheetView.swift */; };
9DD5AA10E85137140FEA86A3 /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
9DD84E014ADFB2DD813022D5 /* RoomDetailsEditScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00E5B2CBEF8F96424F095508 /* RoomDetailsEditScreenViewModelTests.swift */; };
9DE801D278AC34737467F937 /* VoiceMessageMediaManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 889DEDD63C68ABDA8AD29812 /* VoiceMessageMediaManagerProtocol.swift */; };
Expand Down Expand Up @@ -2712,6 +2713,7 @@
E7495E1119753B06FF2C2279 /* PhotoLibraryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoLibraryManager.swift; sourceTree = "<group>"; };
E76A706B3EEA32B882DA5E2D /* BlockedUsersScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockedUsersScreenViewModelProtocol.swift; sourceTree = "<group>"; };
E78FC546F28E045A560F2963 /* EncryptionKeyProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionKeyProviderProtocol.swift; sourceTree = "<group>"; };
E7DA8FFD31B18324CC04A823 /* SpacesAnnouncementSheetView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpacesAnnouncementSheetView.swift; sourceTree = "<group>"; };
E8294DB9E95C0C0630418466 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = "<group>"; };
E8495F37D6245AD0CFA1F60B /* AppLockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTests.swift; sourceTree = "<group>"; };
E8A1F98AE670377B20679FF5 /* MediaPlayerProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayerProvider.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -6557,6 +6559,7 @@
isa = PBXGroup;
children = (
F52DA8CCCABA0998C8AA273C /* SpaceListScreen.swift */,
E7DA8FFD31B18324CC04A823 /* SpacesAnnouncementSheetView.swift */,
);
path = View;
sourceTree = "<group>";
Expand Down Expand Up @@ -8311,6 +8314,7 @@
94C2B531B96493B68B976E5F /* SpaceServiceProxy.swift in Sources */,
A2091F4B1332D9BF273B09D5 /* SpaceServiceProxyMock.swift in Sources */,
DB5200B87C4CE9DF0024AC4E /* SpaceServiceProxyProtocol.swift in Sources */,
9DB4B303ECC05F0F33582594 /* SpacesAnnouncementSheetView.swift in Sources */,
DF004A5B2EABBD0574D06A04 /* SplashScreenCoordinator.swift in Sources */,
E1C67E5D9E22135A8FEBBD60 /* StackedAvatarsView.swift in Sources */,
3DAF325D8AE461F7CDB282BD /* StartChatScreen.swift in Sources */,
Expand Down
4 changes: 4 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 hasSeenSpacesAnnouncement
case hasSeenNewSoundBanner
case appLockNumberOfPINAttempts
case appLockNumberOfBiometricAttempts
Expand Down Expand Up @@ -162,6 +163,9 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.seenInvites, defaultValue: [], storageType: .userDefaults(store))
var seenInvites: Set<String>

@UserPreference(key: UserDefaultsKeys.hasSeenSpacesAnnouncement, defaultValue: false, storageType: .userDefaults(store))
var hasSeenSpacesAnnouncement

/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ class SpaceExplorerFlowCoordinator: FlowCoordinatorProtocol {
private func presentSpaceList() {
let parameters = SpaceListScreenCoordinatorParameters(userSession: userSession,
selectedSpacePublisher: selectedSpaceSubject.asCurrentValuePublisher(),
appSettings: flowParameters.appSettings,
userIndicatorController: flowParameters.userIndicatorController)
let coordinator = SpaceListScreenCoordinator(parameters: parameters)
coordinator.actionsPublisher
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ enum TestablePreviewsDictionary {
"SpaceListScreen_Previews" : SpaceListScreen_Previews.self,
"SpaceRoomCell_Previews" : SpaceRoomCell_Previews.self,
"SpaceScreen_Previews" : SpaceScreen_Previews.self,
"SpacesAnnouncementSheetView_Previews" : SpacesAnnouncementSheetView_Previews.self,
"SplashScreen_Previews" : SplashScreen_Previews.self,
"StackedAvatarsView_Previews" : StackedAvatarsView_Previews.self,
"StartChatScreen_Previews" : StartChatScreen_Previews.self,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import SwiftUI
struct SpaceListScreenCoordinatorParameters {
let userSession: UserSessionProtocol
let selectedSpacePublisher: CurrentValuePublisher<String?, Never>
let appSettings: AppSettings
let userIndicatorController: UserIndicatorControllerProtocol
}

Expand All @@ -37,6 +38,7 @@ final class SpaceListScreenCoordinator: CoordinatorProtocol {

viewModel = SpaceListScreenViewModel(userSession: parameters.userSession,
selectedSpacePublisher: parameters.selectedSpacePublisher,
appSettings: parameters.appSettings,
userIndicatorController: parameters.userIndicatorController)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,13 @@ struct SpaceListScreenViewState: BindableState {
}
}

struct SpaceListScreenViewStateBindings { }
struct SpaceListScreenViewStateBindings {
var isPresentingFeatureAnnouncement = false
}

enum SpaceListScreenViewAction {
case spaceAction(SpaceRoomCell.Action)
case showSettings
case screenAppeared
case featureAnnouncementAppeared
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ typealias SpaceListScreenViewModelType = StateStoreViewModelV2<SpaceListScreenVi

class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenViewModelProtocol {
private let spaceServiceProxy: SpaceServiceProxyProtocol
private let appSettings: AppSettings
private let userIndicatorController: UserIndicatorControllerProtocol

private let actionsSubject: PassthroughSubject<SpaceListScreenViewModelAction, Never> = .init()
Expand All @@ -21,8 +22,10 @@ class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenVie

init(userSession: UserSessionProtocol,
selectedSpacePublisher: CurrentValuePublisher<String?, Never>,
appSettings: AppSettings,
userIndicatorController: UserIndicatorControllerProtocol) {
spaceServiceProxy = userSession.clientProxy.spaceService
self.appSettings = appSettings
self.userIndicatorController = userIndicatorController

super.init(initialViewState: SpaceListScreenViewState(userID: userSession.clientProxy.userID,
Expand Down Expand Up @@ -62,6 +65,13 @@ class SpaceListScreenViewModel: SpaceListScreenViewModelType, SpaceListScreenVie
fatalError("There shouldn't be any unjoined spaces in the joined spaces list.")
case .showSettings:
actionsSubject.send(.showSettings)
case .screenAppeared:
if !appSettings.hasSeenSpacesAnnouncement {
// Use a task otherwise the presentation isn't animated.
Task { state.bindings.isPresentingFeatureAnnouncement = true }
}
case .featureAnnouncementAppeared:
appSettings.hasSeenSpacesAnnouncement = true
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ struct SpaceListScreen: View {
.toolbar { toolbar }
.background(Color.compound.bgCanvasDefault.ignoresSafeArea())
.bloom()
.onAppear { context.send(viewAction: .screenAppeared) }
.sheet(isPresented: $context.isPresentingFeatureAnnouncement) {
SpacesAnnouncementSheetView(context: context)
}
}

var header: some View {
Expand Down Expand Up @@ -110,6 +114,7 @@ struct SpaceListScreen_Previews: PreviewProvider, TestablePreview {

let viewModel = SpaceListScreenViewModel(userSession: UserSessionMock(.init(clientProxy: clientProxy)),
selectedSpacePublisher: .init(nil),
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock())

return viewModel
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//
// Copyright 2022-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 SpacesAnnouncementSheetView: View {
@Environment(\.dismiss) private var dismiss

let context: SpaceListScreenViewModel.Context

var body: some View {
FullscreenDialog(topPadding: 44, horizontalPadding: 24) {
content
} bottomContent: {
buttons
}
.background()
.backgroundStyle(.compound.bgCanvasDefault)
.padding(.top, 14) // For the drag indicator
.presentationDragIndicator(.visible)
.onAppear { context.send(viewAction: .featureAnnouncementAppeared) }
}

var content: some View {
VStack(spacing: 16) {
BigIcon(icon: \.spaceSolid, style: .defaultSolid)

VStack(spacing: 8) {
HStack(spacing: 6) {
Text(L10n.screenSpaceAnnouncementTitle)
.font(.compound.headingMDBold)
.foregroundStyle(.compound.textPrimary)
.multilineTextAlignment(.center)
Text(L10n.commonBeta)
.font(.compound.bodyXSSemibold)
.foregroundStyle(.compound.textInfoPrimary)
.textCase(.uppercase)
.padding(.horizontal, 10)
.padding(.vertical, 5)
.background {
RoundedRectangle(cornerRadius: 6)
.fill(.compound.bgInfoSubtle)
RoundedRectangle(cornerRadius: 6)
.stroke(.compound.borderInfoSubtle)
}
}
Text(L10n.screenSpaceAnnouncementSubtitle)
.font(.compound.bodyMD)
.foregroundStyle(.compound.textSecondary)
.multilineTextAlignment(.center)
}

visualListItems

Text(L10n.screenSpaceAnnouncementNotice)
.font(.compound.bodyMD)
.foregroundStyle(.compound.textSecondary)
.multilineTextAlignment(.center)
}
}

var visualListItems: some View {
VStack(spacing: 4) {
VisualListItem(title: L10n.screenSpaceAnnouncementItem1, position: .top) {
CompoundIcon(\.visibilityOn)
.foregroundStyle(.compound.iconSecondary)
.alignmentGuide(.top) { _ in 2 }
}
VisualListItem(title: L10n.screenSpaceAnnouncementItem2, position: .middle) {
CompoundIcon(\.email)
.foregroundStyle(.compound.iconSecondary)
.alignmentGuide(.top) { _ in 2 }
}
VisualListItem(title: L10n.screenSpaceAnnouncementItem3, position: .middle) {
CompoundIcon(\.search)
.foregroundStyle(.compound.iconSecondary)
.alignmentGuide(.top) { _ in 2 }
}
// This isn't possible until we enabled the room directory.
// VisualListItem(title: L10n.screenSpaceAnnouncementItem4, position: .middle) {
// CompoundIcon(\.explore)
// .foregroundStyle(.compound.iconSecondary)
// .alignmentGuide(.top) { _ in 2 }
// }
VisualListItem(title: L10n.screenSpaceAnnouncementItem5, position: .bottom) {
CompoundIcon(\.leave)
.foregroundStyle(.compound.iconSecondary)
.alignmentGuide(.top) { _ in 2 }
}
}
}

var buttons: some View {
Button(L10n.actionContinue, action: dismiss.callAsFunction)
.buttonStyle(.compound(.primary))
}
}

// MARK: - Previews

struct SpacesAnnouncementSheetView_Previews: PreviewProvider, TestablePreview {
static let viewModel = SpaceListScreenViewModel(userSession: UserSessionMock(.init()),
selectedSpacePublisher: .init(nil),
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock())

static var previews: some View {
SpacesAnnouncementSheetView(context: viewModel.context)
}
}
1 change: 1 addition & 0 deletions ElementX/Sources/UITests/UITestsAppCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,7 @@ class MockScreen: Identifiable {
appSettings.hasRunNotificationPermissionsOnboarding = true
appSettings.analyticsConsentState = .optedOut
appSettings.spacesEnabled = true
appSettings.hasSeenSpacesAnnouncement = true

let clientProxy = ClientProxyMock(.init(userID: "@mock:client.com",
deviceID: "MOCKCLIENT",
Expand Down
6 changes: 6 additions & 0 deletions PreviewTests/Sources/GeneratedPreviewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,12 @@ extension PreviewTests {
}
}

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

func testSplashScreen() async throws {
for (index, preview) in SplashScreen_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.
32 changes: 32 additions & 0 deletions UnitTests/Sources/SpaceListScreenViewModelTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,22 @@ import XCTest
class SpaceListScreenViewModelTests: XCTestCase {
var joinedSpacesSubject: CurrentValueSubject<[SpaceRoomProxyProtocol], Never>!
var spaceServiceProxy: SpaceServiceProxyMock!
var appSettings: AppSettings!

var viewModel: SpaceListScreenViewModelProtocol!

var context: SpaceListScreenViewModelType.Context {
viewModel.context
}

override func setUp() {
AppSettings.resetAllSettings()
appSettings = AppSettings()
}

override func tearDown() {
AppSettings.resetAllSettings()
}

func testInitialState() {
setupViewModel()
Expand Down Expand Up @@ -59,6 +69,27 @@ class SpaceListScreenViewModelTests: XCTestCase {
}
}

func testFeatureAnnouncement() async throws {
setupViewModel()
XCTAssertFalse(appSettings.hasSeenSpacesAnnouncement)
XCTAssertFalse(context.isPresentingFeatureAnnouncement)

let deferred = deferFulfillment(context.observe(\.isPresentingFeatureAnnouncement)) { $0 == true }
viewModel.context.send(viewAction: .screenAppeared)
try await deferred.fulfill()
XCTAssertTrue(context.isPresentingFeatureAnnouncement)

viewModel.context.send(viewAction: .featureAnnouncementAppeared)
XCTAssertTrue(appSettings.hasSeenSpacesAnnouncement)

context.isPresentingFeatureAnnouncement = false

let deferredFailure = deferFailure(context.observe(\.isPresentingFeatureAnnouncement), timeout: 1) { $0 == true }
viewModel.context.send(viewAction: .screenAppeared)
try await deferredFailure.fulfill()
XCTAssertFalse(context.isPresentingFeatureAnnouncement)
}

// MARK: - Helpers

private func setupViewModel() {
Expand All @@ -80,6 +111,7 @@ class SpaceListScreenViewModelTests: XCTestCase {

viewModel = SpaceListScreenViewModel(userSession: userSession,
selectedSpacePublisher: .init(nil),
appSettings: ServiceLocator.shared.settings,
userIndicatorController: UserIndicatorControllerMock())
}
}
Loading