mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Add the spaces feature announcement sheet. (#4571)
This commit is contained in:
@@ -29,6 +29,7 @@ final class AppSettings {
|
||||
private enum UserDefaultsKeys: String {
|
||||
case lastVersionLaunched
|
||||
case seenInvites
|
||||
case hasSeenSpacesAnnouncement
|
||||
case hasSeenNewSoundBanner
|
||||
case appLockNumberOfPINAttempts
|
||||
case appLockNumberOfBiometricAttempts
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -13,6 +13,7 @@ import SwiftUI
|
||||
struct SpaceListScreenCoordinatorParameters {
|
||||
let userSession: UserSessionProtocol
|
||||
let selectedSpacePublisher: CurrentValuePublisher<String?, Never>
|
||||
let appSettings: AppSettings
|
||||
let userIndicatorController: UserIndicatorControllerProtocol
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ final class SpaceListScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
viewModel = SpaceListScreenViewModel(userSession: parameters.userSession,
|
||||
selectedSpacePublisher: parameters.selectedSpacePublisher,
|
||||
appSettings: parameters.appSettings,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
@@ -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
|
||||
|
||||
+115
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user