From 79112ddcb0366e299b78c7a8fa9ec9642ae42bf0 Mon Sep 17 00:00:00 2001 From: Adam Sharp Date: Mon, 16 Sep 2024 13:59:32 -0400 Subject: [PATCH] Restore missing content during iPad permissions onboarding --- Signal.xcodeproj/project.pbxproj | 4 + ...rovisioningPermissionsViewController.swift | 95 +++---------------- .../RegistrationNavigationController.swift | 4 +- .../RegistrationPermissionsView.swift | 66 ++++++++----- .../translations/en.lproj/Localizable.strings | 9 -- .../ScrollableWhenCompact.swift | 27 ++++++ 6 files changed, 93 insertions(+), 112 deletions(-) create mode 100644 SignalUI/SwiftUIExtensions/ScrollableWhenCompact.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 26fbe7b7e1..e2b62fff93 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 0550A5E22C4035170072CC02 /* CLVViewInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0550A5E12C4035170072CC02 /* CLVViewInfo.swift */; }; 0550A5E42C4048CF0072CC02 /* ChatListFilterFooterCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0550A5E32C4048CF0072CC02 /* ChatListFilterFooterCell.swift */; }; 05572BC42BFC0094006A72F1 /* DoubleTapToEditOnboardingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05572BC32BFC0094006A72F1 /* DoubleTapToEditOnboardingController.swift */; }; + 05594CCE2C989F1900CCBFF6 /* ScrollableWhenCompact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05594CCD2C989F1900CCBFF6 /* ScrollableWhenCompact.swift */; }; 058B49932C66805500307D38 /* AVAssetExportSession+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = 058B49922C66804B00307D38 /* AVAssetExportSession+Async.swift */; }; 059982642C6D0C5200C87533 /* ChatListPinInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 059982632C6D0C4F00C87533 /* ChatListPinInfo.swift */; }; 05B411252C62845000A1EDBC /* ChatListInboxFilterSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 05B411242C62845000A1EDBC /* ChatListInboxFilterSection.swift */; }; @@ -3422,6 +3423,7 @@ 0550A5E12C4035170072CC02 /* CLVViewInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLVViewInfo.swift; sourceTree = ""; }; 0550A5E32C4048CF0072CC02 /* ChatListFilterFooterCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListFilterFooterCell.swift; sourceTree = ""; }; 05572BC32BFC0094006A72F1 /* DoubleTapToEditOnboardingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DoubleTapToEditOnboardingController.swift; sourceTree = ""; }; + 05594CCD2C989F1900CCBFF6 /* ScrollableWhenCompact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollableWhenCompact.swift; sourceTree = ""; }; 058B49922C66804B00307D38 /* AVAssetExportSession+Async.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AVAssetExportSession+Async.swift"; sourceTree = ""; }; 059982632C6D0C4F00C87533 /* ChatListPinInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListPinInfo.swift; sourceTree = ""; }; 05B411242C62845000A1EDBC /* ChatListInboxFilterSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListInboxFilterSection.swift; sourceTree = ""; }; @@ -10555,6 +10557,7 @@ children = ( 05104E392C8B540C00F8851F /* AccessibleLayoutMetric.swift */, 05104D172C8A151100F8851F /* AsyncViewTask.swift */, + 05594CCD2C989F1900CCBFF6 /* ScrollableWhenCompact.swift */, 0510F69D2C91EB2800FA3FDE /* ScrollBounceBehaviorIfAvailable.swift */, B9D721752C87B8EB007EDA85 /* SwiftUI+Animations.swift */, ); @@ -15175,6 +15178,7 @@ 88B987022880890800F8C74D /* SafetyNumberConfirmationSheet.swift in Sources */, 88B9870928808A8A00F8C74D /* ScanQRCodeViewController.swift in Sources */, 7677E41129F7A60500AC6A75 /* ScreenLockViewController.swift in Sources */, + 05594CCE2C989F1900CCBFF6 /* ScrollableWhenCompact.swift in Sources */, 0510F69E2C91EB3000FA3FDE /* ScrollBounceBehaviorIfAvailable.swift in Sources */, 50597BBF2B97D629004681E1 /* SearchableNameFinder.swift in Sources */, 66FC638E29EDABAC00F00DAC /* SearchDisplayConfigurations.swift in Sources */, diff --git a/Signal/Provisioning/UserInterface/ProvisioningPermissionsViewController.swift b/Signal/Provisioning/UserInterface/ProvisioningPermissionsViewController.swift index 49e1abdcca..1b8596c748 100644 --- a/Signal/Provisioning/UserInterface/ProvisioningPermissionsViewController.swift +++ b/Signal/Provisioning/UserInterface/ProvisioningPermissionsViewController.swift @@ -3,104 +3,39 @@ // SPDX-License-Identifier: AGPL-3.0-only // -import Contacts -import Lottie import SignalServiceKit import SignalUI -public class ProvisioningPermissionsViewController: ProvisioningBaseViewController { - - private let animationView = LottieAnimationView(name: "notificationPermission") - - override public func loadView() { +class ProvisioningPermissionsViewController: ProvisioningBaseViewController { + override func loadView() { view = UIView() + view.backgroundColor = Theme.backgroundColor view.addSubview(primaryView) primaryView.autoPinEdgesToSuperviewEdges() - view.backgroundColor = Theme.backgroundColor - - let titleText: String - let explanationText: String - let giveAccessText: String - titleText = OWSLocalizedString( - "LINKED_ONBOARDING_PERMISSIONS_TITLE", - comment: "Title of the 'onboarding permissions' view." - ) - explanationText = OWSLocalizedString( - "LINKED_ONBOARDING_PERMISSIONS_EXPLANATION", - comment: "Explanation in the 'onboarding permissions' view." - ) - giveAccessText = OWSLocalizedString( - "LINKED_ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON", - comment: "Label for the 'give access' button in the 'onboarding permissions' view." - ) - - let titleLabel = self.createTitleLabel(text: titleText) - titleLabel.accessibilityIdentifier = "onboarding.permissions." + "titleLabel" - titleLabel.setCompressionResistanceVerticalHigh() - - let explanationLabel = self.createExplanationLabel(explanationText: explanationText) - explanationLabel.accessibilityIdentifier = "onboarding.permissions." + "explanationLabel" - explanationLabel.setCompressionResistanceVerticalHigh() - - let giveAccessButton = self.primaryButton(title: giveAccessText, selector: #selector(giveAccessPressed)) - giveAccessButton.accessibilityIdentifier = "onboarding.permissions." + "giveAccessButton" - giveAccessButton.setCompressionResistanceVerticalHigh() - - animationView.loopMode = .playOnce - animationView.backgroundBehavior = .pauseAndRestore - animationView.contentMode = .scaleAspectFit - animationView.setContentHuggingHigh() - - let stackView = UIStackView(arrangedSubviews: [ - titleLabel, - UIView.spacer(withHeight: 20), - explanationLabel, - UIView.spacer(withHeight: 60), - animationView, - UIView.vStretchingSpacer(minHeight: 80), - ProvisioningBaseViewController.horizontallyWrap(primaryButton: giveAccessButton) - ]) - stackView.axis = .vertical - stackView.alignment = .fill - stackView.spacing = 0 - primaryView.addSubview(stackView) - - stackView.autoPinEdgesToSuperviewMargins() + let content = RegistrationPermissionsViewController(requestingContactsAuthorization: false, presenter: self) + addChild(content) + primaryView.addSubview(content.view) + content.view.autoPinEdgesToSuperviewMargins() + content.didMove(toParent: self) } - override public func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - self.animationView.play() - } - - // MARK: Requesting permissions - - public func needsToAskForAnyPermissions() -> Guarantee { - return Guarantee { resolve in + func needsToAskForAnyPermissions() -> Guarantee { + Guarantee { resolve in UNUserNotificationCenter.current().getNotificationSettings { settings in resolve(settings.authorizationStatus == .notDetermined) } } } +} - private func requestPermissions() { +extension ProvisioningPermissionsViewController: RegistrationPermissionsPresenter { + func requestPermissions() async { Logger.info("") // If you request any additional permissions, make sure to add them to // `needsToAskForAnyPermissions`. - Task { @MainActor in - await PushRegistrationManager.shared.registerUserNotificationSettings() - self.provisioningController.provisioningPermissionsDidComplete(viewController: self) - } - } - - // MARK: - Events - - @objc - private func giveAccessPressed() { - Logger.info("") - - requestPermissions() + await PushRegistrationManager.shared.registerUserNotificationSettings() + provisioningController.provisioningPermissionsDidComplete(viewController: self) } } diff --git a/Signal/Registration/UserInterface/RegistrationNavigationController.swift b/Signal/Registration/UserInterface/RegistrationNavigationController.swift index a09c09c221..90080b91e3 100644 --- a/Signal/Registration/UserInterface/RegistrationNavigationController.swift +++ b/Signal/Registration/UserInterface/RegistrationNavigationController.swift @@ -147,7 +147,9 @@ public class RegistrationNavigationController: OWSNavigationController { case .permissions: return Controller( type: RegistrationPermissionsViewController.self, - make: RegistrationPermissionsViewController.init(presenter:), + make: { presenter in + RegistrationPermissionsViewController(requestingContactsAuthorization: true, presenter: presenter) + }, // The state never changes here. In theory we would build // state update support in the permissions controller, // but its overkill so we have not. diff --git a/Signal/Registration/UserInterface/RegistrationPermissionsView.swift b/Signal/Registration/UserInterface/RegistrationPermissionsView.swift index 2c7fe09124..64445ecb89 100644 --- a/Signal/Registration/UserInterface/RegistrationPermissionsView.swift +++ b/Signal/Registration/UserInterface/RegistrationPermissionsView.swift @@ -12,26 +12,29 @@ protocol RegistrationPermissionsPresenter { func requestPermissions() async } -final class RegistrationPermissionsViewController: UIHostingController { - init(presenter: any RegistrationPermissionsPresenter) { - super.init(rootView: RegistrationPermissionsView(presenter: presenter)) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - struct RegistrationPermissionsView: View { + var requestingContactsAuthorization: Bool var presenter: any RegistrationPermissionsPresenter @State private var requestPermissions: RequestPermissionsTask? @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @Environment(\.verticalSizeClass) private var verticalSizeClass @AccessibleLayoutMetric private var headerPadding = 16 @AccessibleLayoutMetric private var headerSpacing = 12 @AccessibleLayoutMetric(scale: 0.5) private var sectionSpacing = 64 + private var isCompactLayout: Bool { + horizontalSizeClass == .compact || verticalSizeClass == .compact + } + + private var layoutMargins: EdgeInsets { + if isCompactLayout { + EdgeInsets(.layoutMarginsForRegistration(UIUserInterfaceSizeClass(horizontalSizeClass))) + } else { + EdgeInsets() + } + } + var body: some View { VStack { VStack(spacing: headerSpacing) { @@ -44,7 +47,7 @@ struct RegistrationPermissionsView: View { .multilineTextAlignment(.center) .padding(.horizontal, headerPadding) - ScrollView { + ScrollableWhenCompact { VStack { Spacer(minLength: sectionSpacing) .frame(maxHeight: $sectionSpacing.rawValue) @@ -59,14 +62,18 @@ struct RegistrationPermissionsView: View { PermissionIcon(.bellRing) } - PermissionDescription { - Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_TITLE", comment: "Title introducing the 'Contacts' permission in the 'onboarding permissions' view.")) - } description: { - Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_DESCRIPTION", comment: "Description of the 'Contacts' permission in the 'onboarding permissions' view.")) - } icon: { - PermissionIcon(.personCircleLarge) + if requestingContactsAuthorization { + PermissionDescription { + Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_TITLE", comment: "Title introducing the 'Contacts' permission in the 'onboarding permissions' view.")) + } description: { + Text(OWSLocalizedString("ONBOARDING_PERMISSIONS_CONTACTS_DESCRIPTION", comment: "Description of the 'Contacts' permission in the 'onboarding permissions' view.")) + } icon: { + PermissionIcon(.personCircleLarge) + } } } + // Expand to available width when compact, otherwise horizontally center. + .frame(maxWidth: isCompactLayout ? .infinity : nil, alignment: .leading) Spacer(minLength: sectionSpacing) .layoutPriority(-1) @@ -78,11 +85,16 @@ struct RegistrationPermissionsView: View { .dynamicTypeSize(...DynamicTypeSize.accessibility2) .frame(maxWidth: 400) } - .padding(EdgeInsets(.layoutMarginsForRegistration(UIUserInterfaceSizeClass(horizontalSizeClass)))) + .padding(layoutMargins) } - .scrollBounceBehaviorIfAvailable(.basedOnSize) } .foregroundStyle(Color.Signal.label, Color.Signal.secondaryLabel, Color.Signal.tertiaryLabel) + // Use larger text size on iPad. + .transformEnvironment(\.dynamicTypeSize) { dynamicTypeSize in + if !isCompactLayout, dynamicTypeSize < .xLarge { + dynamicTypeSize = .xLarge + } + } .dynamicTypeSize(...DynamicTypeSize.accessibility3) .minimumScaleFactor(0.9) .navigationBarBackButtonHidden() @@ -150,7 +162,6 @@ private extension RegistrationPermissionsView { .foregroundStyle(.secondary) .layoutPriority(1) } - Spacer() } } } @@ -181,6 +192,17 @@ private extension RegistrationPermissionsView { } } +final class RegistrationPermissionsViewController: UIHostingController { + init(requestingContactsAuthorization: Bool, presenter: any RegistrationPermissionsPresenter) { + super.init(rootView: RegistrationPermissionsView(requestingContactsAuthorization: requestingContactsAuthorization, presenter: presenter)) + } + + @available(*, unavailable) + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + #if DEBUG private struct PreviewPermissionsPresenter: RegistrationPermissionsPresenter { func requestPermissions() async { @@ -191,7 +213,7 @@ private struct PreviewPermissionsPresenter: RegistrationPermissionsPresenter { #Preview { VStack { Color.clear.frame(height: 44) - RegistrationPermissionsView(presenter: PreviewPermissionsPresenter()) + RegistrationPermissionsView(requestingContactsAuthorization: true, presenter: PreviewPermissionsPresenter()) } } #endif diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 34334bc137..373442ce62 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -3937,15 +3937,6 @@ /* Menu item and navbar title for the device manager */ "LINKED_DEVICES_TITLE" = "Linked Devices"; -/* Label for the 'give access' button in the 'onboarding permissions' view. */ -"LINKED_ONBOARDING_PERMISSIONS_ENABLE_PERMISSIONS_BUTTON" = "Allow Notifications"; - -/* Explanation in the 'onboarding permissions' view. */ -"LINKED_ONBOARDING_PERMISSIONS_EXPLANATION" = "Allowing notifications lets you know when you receive messages, calls or updates about new conversation activity."; - -/* Title of the 'onboarding permissions' view. */ -"LINKED_ONBOARDING_PERMISSIONS_TITLE" = "Allow Notifications"; - /* Alert Title */ "LINKING_DEVICE_FAILED_TITLE" = "Linking Device Failed"; diff --git a/SignalUI/SwiftUIExtensions/ScrollableWhenCompact.swift b/SignalUI/SwiftUIExtensions/ScrollableWhenCompact.swift new file mode 100644 index 0000000000..aebdfefce0 --- /dev/null +++ b/SignalUI/SwiftUIExtensions/ScrollableWhenCompact.swift @@ -0,0 +1,27 @@ +// +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +// + +import SwiftUI + +public struct ScrollableWhenCompact: View { + @Environment(\.verticalSizeClass) private var verticalSizeClass + + private var content: Content + + public init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + public var body: some View { + if verticalSizeClass == .compact { + ScrollView { + content + } + .scrollBounceBehaviorIfAvailable(.basedOnSize) + } else { + content + } + } +}