mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Add the multi-account announcement screen (#6263)
* Add the multi-account announcement screen A self-sizing bottom sheet telling users they can now add multiple accounts, matching the designs. Close and Add account both report back to the coordinator; nothing presents it yet. * Use the Localazy strings for the multi-account announcement The copy now lives in Localazy as screen.multi_account_announcement.*, named after the screen rather than the room list. * Apply review suggestions to the multi-account announcement - Use an ElementNavigationStack with a toolbar close button, like RedactConfirmationView, instead of an overlaid glass button. * Remove the large text preview from the multi-account announcement Other sheets built the same way don't snapshot Dynamic Type sizes, and the iOS 26 bar material it would show isn't rendered in snapshots. * Use TitleAndIcon for the multi-account announcement TitleAndIcon now takes an attributed subtitle (#6274), so the sheet no longer needs to rebuild its layout by hand. The snapshots are unchanged.
This commit is contained in:
@@ -1074,6 +1074,9 @@
|
||||
"screen_moderation_and_safety_invites_heading" = "Invites";
|
||||
"screen_moderation_and_safety_other_users_heading" = "Other users";
|
||||
"screen_moderation_and_safety_share_presence_heading" = "Your appearance";
|
||||
"screen_multi_account_announcement_action" = "Add account";
|
||||
"screen_multi_account_announcement_description" = "Go to %1$@ to sign into another account. Then you can easily switch between them.";
|
||||
"screen_multi_account_announcement_title" = "You can now add multiple accounts";
|
||||
"screen_notification_optin_subtitle" = "You can change your settings later.";
|
||||
"screen_notification_optin_title" = "Allow notifications and never miss a message";
|
||||
"screen_notification_settings_additional_settings_section_title" = "Additional settings";
|
||||
|
||||
@@ -1074,6 +1074,9 @@
|
||||
"screen_moderation_and_safety_invites_heading" = "Invites";
|
||||
"screen_moderation_and_safety_other_users_heading" = "Other users";
|
||||
"screen_moderation_and_safety_share_presence_heading" = "Your appearance";
|
||||
"screen_multi_account_announcement_action" = "Add account";
|
||||
"screen_multi_account_announcement_description" = "Go to %1$@ to sign into another account. Then you can easily switch between them.";
|
||||
"screen_multi_account_announcement_title" = "You can now add multiple accounts";
|
||||
"screen_notification_optin_subtitle" = "You can change your settings later.";
|
||||
"screen_notification_optin_title" = "Allow notifications and never miss a message";
|
||||
"screen_notification_settings_additional_settings_section_title" = "Additional settings";
|
||||
|
||||
@@ -2518,6 +2518,14 @@ internal nonisolated enum L10n {
|
||||
internal static var screenModerationAndSafetyOtherUsersHeading: String { return L10n.tr("Localizable", "screen_moderation_and_safety_other_users_heading") }
|
||||
/// Your appearance
|
||||
internal static var screenModerationAndSafetySharePresenceHeading: String { return L10n.tr("Localizable", "screen_moderation_and_safety_share_presence_heading") }
|
||||
/// Add account
|
||||
internal static var screenMultiAccountAnnouncementAction: String { return L10n.tr("Localizable", "screen_multi_account_announcement_action") }
|
||||
/// Go to %1$@ to sign into another account. Then you can easily switch between them.
|
||||
internal static func screenMultiAccountAnnouncementDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_multi_account_announcement_description", String(describing: p1))
|
||||
}
|
||||
/// You can now add multiple accounts
|
||||
internal static var screenMultiAccountAnnouncementTitle: String { return L10n.tr("Localizable", "screen_multi_account_announcement_title") }
|
||||
/// You can change your settings later.
|
||||
internal static var screenNotificationOptinSubtitle: String { return L10n.tr("Localizable", "screen_notification_optin_subtitle") }
|
||||
/// Allow notifications and never miss a message
|
||||
|
||||
@@ -102,6 +102,7 @@ enum TestablePreviewsDictionary {
|
||||
"MessageComposer_Previews" : MessageComposer_Previews.self,
|
||||
"MessageForwardingScreen_Previews" : MessageForwardingScreen_Previews.self,
|
||||
"MessageText_Previews" : MessageText_Previews.self,
|
||||
"MultiAccountAnnouncementScreen_Previews" : MultiAccountAnnouncementScreen_Previews.self,
|
||||
"NoticeRoomTimelineView_Previews" : NoticeRoomTimelineView_Previews.self,
|
||||
"NotificationPermissionsScreen_Previews" : NotificationPermissionsScreen_Previews.self,
|
||||
"NotificationSettingsEditScreenRoomCell_Previews" : NotificationSettingsEditScreenRoomCell_Previews.self,
|
||||
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum MultiAccountAnnouncementScreenCoordinatorAction {
|
||||
case addAccount
|
||||
case dismiss
|
||||
}
|
||||
|
||||
final class MultiAccountAnnouncementScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: MultiAccountAnnouncementScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<MultiAccountAnnouncementScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<MultiAccountAnnouncementScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
viewModel = MultiAccountAnnouncementScreenViewModel()
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .addAccount:
|
||||
actionsSubject.send(.addAccount)
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(MultiAccountAnnouncementScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum MultiAccountAnnouncementScreenViewModelAction {
|
||||
case addAccount
|
||||
case dismiss
|
||||
}
|
||||
|
||||
struct MultiAccountAnnouncementScreenViewState: BindableState {
|
||||
let description: AttributedString
|
||||
|
||||
init() {
|
||||
let boldPlaceholder = "{bold}"
|
||||
var description = AttributedString(L10n.screenMultiAccountAnnouncementDescription(boldPlaceholder))
|
||||
var boldString = AttributedString("\(L10n.commonSettings) > \(L10n.screenSettingsAddAccount)")
|
||||
boldString.bold()
|
||||
description.replace(boldPlaceholder, with: boldString)
|
||||
self.description = description
|
||||
}
|
||||
}
|
||||
|
||||
enum MultiAccountAnnouncementScreenViewAction {
|
||||
case addAccount
|
||||
case close
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias MultiAccountAnnouncementScreenViewModelType = StateStoreViewModelV2<MultiAccountAnnouncementScreenViewState, MultiAccountAnnouncementScreenViewAction>
|
||||
|
||||
class MultiAccountAnnouncementScreenViewModel: MultiAccountAnnouncementScreenViewModelType, MultiAccountAnnouncementScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<MultiAccountAnnouncementScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<MultiAccountAnnouncementScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: .init())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: MultiAccountAnnouncementScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .addAccount:
|
||||
actionsSubject.send(.addAccount)
|
||||
case .close:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
protocol MultiAccountAnnouncementScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<MultiAccountAnnouncementScreenViewModelAction, Never> { get }
|
||||
var context: MultiAccountAnnouncementScreenViewModelType.Context { get }
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// Copyright 2026 Element Creations 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 MultiAccountAnnouncementScreen: View {
|
||||
let context: MultiAccountAnnouncementScreenViewModel.Context
|
||||
|
||||
@State private var contentHeight: CGFloat = .zero
|
||||
@State private var buttonHeight: CGFloat = .zero
|
||||
private let topPadding: CGFloat = 44 // For the navigation bar
|
||||
|
||||
var body: some View {
|
||||
ElementNavigationStack {
|
||||
ScrollView {
|
||||
content
|
||||
.readHeight($contentHeight)
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.backportSafeAreaBar(edge: .bottom, spacing: 0) {
|
||||
addAccountButton
|
||||
.readHeight($buttonHeight)
|
||||
}
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .primaryAction) {
|
||||
ToolbarButton(role: .close) { context.send(viewAction: .close) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.height(contentHeight + buttonHeight + topPadding)])
|
||||
.presentationDragIndicator(.hidden)
|
||||
.presentationBackground(.compound.bgCanvasDefault)
|
||||
}
|
||||
|
||||
private var content: some View {
|
||||
TitleAndIcon(title: L10n.screenMultiAccountAnnouncementTitle,
|
||||
subtitle: context.viewState.description,
|
||||
icon: \.userProfileSolid,
|
||||
iconStyle: .defaultSolid)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.top, 16)
|
||||
.padding(.bottom, 24)
|
||||
}
|
||||
|
||||
private var addAccountButton: some View {
|
||||
Button {
|
||||
context.send(viewAction: .addAccount)
|
||||
} label: {
|
||||
Label(L10n.screenMultiAccountAnnouncementAction, icon: \.plus)
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.padding(16)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct MultiAccountAnnouncementScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = MultiAccountAnnouncementScreenViewModel()
|
||||
|
||||
static var previews: some View {
|
||||
MultiAccountAnnouncementScreen(context: viewModel.context)
|
||||
.previewDisplayName("Default")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user