Use modern looking buttons in backup flow on iOS 26.

This commit is contained in:
Igor Solomennikov 2025-10-28 09:57:10 -07:00 committed by GitHub
parent 43f0270483
commit dab7b8a3ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 82 additions and 156 deletions

View File

@ -73,71 +73,45 @@ class BackupRecordKeyViewController: OWSViewController, OWSNavigationChildContro
let heroIconView = UIImageView()
heroIconView.image = .backupsLock
heroIconView.contentMode = .center
heroIconView.autoSetDimensions(to: .square(80))
heroIconView.contentMode = .scaleAspectFit
let headlineLabel = UILabel()
headlineLabel.text = OWSLocalizedString(
let headlineLabel = UILabel.title1Label(text: OWSLocalizedString(
"BACKUP_RECORD_KEY_TITLE",
comment: "Title for a view allowing users to record their 'Recovery Key'."
)
headlineLabel.font = .dynamicTypeTitle1.semibold()
headlineLabel.textColor = .Signal.label
headlineLabel.numberOfLines = 0
headlineLabel.textAlignment = .center
))
let subheadlineLabel = UILabel()
subheadlineLabel.text = OWSLocalizedString(
let subheadlineLabel = UILabel.explanationTextLabel(text: OWSLocalizedString(
"BACKUP_RECORD_KEY_SUBTITLE",
comment: "Subtitle for a view allowing users to record their 'Recovery Key'."
)
subheadlineLabel.font = .dynamicTypeBody
subheadlineLabel.textColor = .Signal.secondaryLabel
subheadlineLabel.numberOfLines = 0
subheadlineLabel.textAlignment = .center
))
let aepTextView = AccountEntropyPoolTextView(mode: .display(aep: aep))
aepTextView.backgroundColor = .Signal.secondaryGroupedBackground
let copyToClipboardButton = UIButton(
configuration: {
var configuration: UIButton.Configuration = .plain()
configuration.attributedTitle = AttributedString(
OWSLocalizedString(
"BACKUP_RECORD_KEY_COPY_TO_CLIPBOARD_BUTTON_TITLE",
comment: "Title for a button allowing users to copy their 'Recovery Key' to the clipboard."
),
attributes: AttributeContainer([
.font: UIFont.dynamicTypeFootnote.medium(),
.foregroundColor: UIColor.Signal.label,
])
)
configuration.background.backgroundColor = .Signal.secondaryFill
configuration.cornerStyle = .capsule
configuration.contentInsets = .init(hMargin: 12, vMargin: 8)
return configuration
}(),
configuration: .compactGray(title: OWSLocalizedString(
"BACKUP_RECORD_KEY_COPY_TO_CLIPBOARD_BUTTON_TITLE",
comment: "Title for a button allowing users to copy their 'Recovery Key' to the clipboard."
)),
primaryAction: UIAction { [weak self] _ in
self?.copyToClipboard()
}
)
copyToClipboardButton.translatesAutoresizingMaskIntoConstraints = false
let copyButtonContainer = UIView.container()
copyButtonContainer.addSubview(copyToClipboardButton)
copyButtonContainer.addConstraints([
copyToClipboardButton.topAnchor.constraint(equalTo: copyButtonContainer.topAnchor),
copyToClipboardButton.leadingAnchor.constraint(greaterThanOrEqualTo: copyToClipboardButton.leadingAnchor),
copyToClipboardButton.centerXAnchor.constraint(equalTo: copyButtonContainer.centerXAnchor),
copyToClipboardButton.bottomAnchor.constraint(equalTo: copyButtonContainer.bottomAnchor),
])
let createNewKeyButton = UIButton(
configuration: {
var configuration: UIButton.Configuration = .plain()
configuration.attributedTitle = AttributedString(
OWSLocalizedString(
"BACKUP_RECORD_KEY_CREATE_NEW_KEY_BUTTON_TITLE",
comment: "Title for a button allowing users to create a new 'Recovery Key'."
),
attributes: AttributeContainer([
.font: UIFont.dynamicTypeHeadline,
.foregroundColor: UIColor.Signal.accent,
])
)
return configuration
}(),
configuration: .largeSecondary(title: OWSLocalizedString(
"BACKUP_RECORD_KEY_CREATE_NEW_KEY_BUTTON_TITLE",
comment: "Title for a button allowing users to create a new 'Recovery Key'."
)),
primaryAction: UIAction { [weak self] _ in
guard let self else { return }
onCreateNewKeyPressedBlock(self)
@ -145,79 +119,37 @@ class BackupRecordKeyViewController: OWSViewController, OWSNavigationChildContro
)
let continueButton = UIButton(
configuration: {
var configuration: UIButton.Configuration = .plain()
configuration.attributedTitle = AttributedString(
CommonStrings.continueButton,
attributes: AttributeContainer([
.font: UIFont.dynamicTypeHeadline,
.foregroundColor: UIColor.white,
])
)
configuration.contentInsets = .init(hMargin: 60, vMargin: 14)
configuration.background.cornerRadius = 12
configuration.background.backgroundColor = .Signal.accent
return configuration
}(),
configuration: .largePrimary(title: CommonStrings.continueButton),
primaryAction: UIAction { [weak self] _ in
guard let self else { return }
onContinuePressedBlock(self)
},
)
let bottomButtonSpacer = UIView(forAutoLayout: ())
bottomButtonSpacer.setContentHuggingPriority(.defaultLow, for: .vertical)
let scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView.autoPinEdgesToSuperviewEdges()
let stackView = UIStackView(arrangedSubviews: [
heroIconView,
headlineLabel,
subheadlineLabel,
aepTextView,
copyToClipboardButton,
bottomButtonSpacer,
])
if options.contains(.showCreateNewKeyButton) {
stackView.addArrangedSubview(createNewKeyButton)
}
if options.contains(.showContinueButton) {
stackView.addArrangedSubview(continueButton)
}
scrollView.addSubview(stackView)
stackView.axis = .vertical
stackView.alignment = .center
let stackView = addStaticContentStackView(
arrangedSubviews: [
heroIconView,
headlineLabel,
subheadlineLabel,
aepTextView,
copyButtonContainer,
.vStretchingSpacer()
],
isScrollable: true
)
stackView.spacing = 24
stackView.setCustomSpacing(32, after: aepTextView)
headlineLabel.autoPinEdge(toSuperviewEdge: .leading, withInset: 8)
headlineLabel.autoPinEdge(toSuperviewEdge: .trailing, withInset: 8)
subheadlineLabel.autoPinEdge(toSuperviewEdge: .leading, withInset: 24)
subheadlineLabel.autoPinEdge(toSuperviewEdge: .trailing, withInset: 24)
let topInset: CGFloat = 20
let bottomInset: CGFloat = 16
stackView.autoPinEdge(.leading, to: .leading, of: view, withOffset: 24)
stackView.autoPinEdge(.trailing, to: .trailing, of: view, withOffset: -24)
stackView.autoPinEdge(toSuperviewEdge: .top, withInset: topInset)
stackView.autoPinEdge(toSuperviewEdge: .bottom, withInset: bottomInset)
NSLayoutConstraint.activate([
stackView.heightAnchor.constraint(
greaterThanOrEqualTo: view.safeAreaLayoutGuide.heightAnchor,
constant: -(topInset + bottomInset),
)
])
/*
bottomButtonSpacer.backgroundColor = .brown
bottomButtonSpacer.widthAnchor.constraint(equalToConstant: 10).isActive = true
stackView.backgroundColor = .green
scrollView.backgroundColor = .orange
*/
var bottomButtons = [UIButton]()
if options.contains(.showCreateNewKeyButton) {
bottomButtons.append(createNewKeyButton)
}
if options.contains(.showContinueButton) {
bottomButtons.append(continueButton)
}
if !bottomButtons.isEmpty {
stackView.addArrangedSubview(bottomButtons.enclosedInVerticalStackView(isFullWidthButtons: options.contains(.showContinueButton)))
}
}
private func copyToClipboard() {

View File

@ -315,7 +315,7 @@ struct ChooseBackupPlanView: View {
}
)
}
.padding(.horizontal, 24)
.padding(.horizontal, 16)
} pinnedFooter: {
Button {
viewModel.confirmSelection()
@ -347,17 +347,12 @@ struct ChooseBackupPlanView: View {
}
Text(text)
.foregroundStyle(.white)
.font(.headline)
.padding(.vertical, 14)
.frame(maxWidth: .infinity)
.background(Color.Signal.ultramarine)
}
.disabled(viewModel.planSelection == viewModel.initialPlanSelection)
.buttonStyle(.plain)
.cornerRadius(12)
.padding(.horizontal, 40)
.buttonStyle(Registration.UI.LargePrimaryButtonStyle())
.padding(.horizontal, 24)
}
.padding(.horizontal)
.multilineTextAlignment(.center)
.background(Color.Signal.groupedBackground)
}

View File

@ -73,12 +73,10 @@ struct BackupOnboardingIntroView: View {
.multilineTextAlignment(.leading)
}
.foregroundColor(Color.Signal.label)
.padding(.horizontal, 10)
.padding(.vertical, 10)
.padding(.all, 10)
.background(Color.Signal.quaternaryFill)
.cornerRadius(12)
}
.padding(.horizontal, 10)
VStack {
Spacer().frame(height: 20)
@ -106,6 +104,7 @@ struct BackupOnboardingIntroView: View {
)
.foregroundStyle(Color.Signal.label)
}
.multilineTextAlignment(.center)
Spacer().frame(height: 12)
@ -115,37 +114,35 @@ struct BackupOnboardingIntroView: View {
))
.font(.body)
.foregroundStyle(Color.Signal.secondaryLabel)
.multilineTextAlignment(.center)
Spacer().frame(height: 32)
VStack(alignment: .leading, spacing: 24) {
ForEach(bulletPoints) { bulletPoint in
Label {
Text(bulletPoint.text)
} icon: {
Image(uiImage: bulletPoint.image)
HStack {
Label {
Text(bulletPoint.text)
} icon: {
Image(uiImage: bulletPoint.image)
}
Spacer()
}
.multilineTextAlignment(.leading)
.foregroundStyle(Color.Signal.label)
.padding(.horizontal, 20) // Extra inset in case of wrap
}
}
.frame(maxWidth: .infinity)
.foregroundStyle(Color.Signal.label)
.padding(.horizontal)
}
.padding(.horizontal, 48)
} pinnedFooter: {
Button {
onContinue()
} label: {
Text(CommonStrings.continueButton)
.foregroundStyle(.white)
.font(.headline)
.padding(.vertical, 14)
.frame(maxWidth: .infinity)
.background(Color.Signal.ultramarine)
}
.buttonStyle(.plain)
.cornerRadius(12)
.padding(.horizontal, 40)
.buttonStyle(Registration.UI.LargePrimaryButtonStyle())
.padding(.horizontal, 24)
Spacer().frame(height: 16)
@ -153,15 +150,11 @@ struct BackupOnboardingIntroView: View {
onNotNow()
} label: {
Text(CommonStrings.notNowButton)
.foregroundStyle(Color.Signal.ultramarine)
.font(.headline)
.padding(.vertical, 14)
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity)
.padding(.horizontal, 40)
.buttonStyle(Registration.UI.LargeSecondaryButtonStyle())
.padding(.horizontal, 24)
}
.multilineTextAlignment(.center)
.padding(.horizontal)
.background(Color.Signal.groupedBackground)
}
}

View File

@ -115,7 +115,7 @@ struct BackupOnboardingKeyIntroView: View {
}
}
}
.padding(.horizontal, 48)
.padding(.horizontal)
} pinnedFooter: {
Button {
viewModel.onContinue()
@ -124,17 +124,12 @@ struct BackupOnboardingKeyIntroView: View {
"BACKUP_ONBOARDING_KEY_INTRO_CONTINUE_BUTTON_TITLE",
comment: "Title for a continue button for a view introducing the 'Recovery Key' during an onboarding flow."
))
.foregroundStyle(.white)
.font(.headline)
.padding(.vertical, 14)
.frame(maxWidth: .infinity)
.background(Color.Signal.ultramarine)
}
.buttonStyle(.plain)
.cornerRadius(12)
.padding(.horizontal, 40)
.buttonStyle(Registration.UI.LargePrimaryButtonStyle())
.padding(.horizontal, 24)
}
.multilineTextAlignment(.center)
.padding(.horizontal)
.background(Color.Signal.groupedBackground)
}
}

View File

@ -250,6 +250,17 @@ public extension UIButton.Configuration {
configuration.baseBackgroundColor = .clear
return configuration
}
static func compactGray(title: String) -> Self {
var configuration = UIButton.Configuration.gray()
configuration.title = title
configuration.titleAlignment = .center
configuration.titleTextAttributesTransformer = .defaultFont(.dynamicTypeFootnoteClamped.medium())
configuration.contentInsets = .smallButtonContentInsets
configuration.baseForegroundColor = .Signal.label
configuration.background.backgroundColor = .Signal.secondaryFill
return configuration
}
}
// MARK: - UIBarButtonItem