diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2025-05-30 11:34:42 +0200 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2025-05-30 11:34:42 +0200 |
| commit | c941655b88492ae7dec02bbc965d5f08eac8dc8d (patch) | |
| tree | ec3f39acc4e1cc54ad9392d30a167abcae1d1f23 | |
| parent | 42b53b4bcd3d3155d42ebca7e33c9450ca5dab73 (diff) | |
| parent | 8b77bdbdf51a99b9b833bc56a8d67fdeb46fc581 (diff) | |
| download | mullvadvpn-c941655b88492ae7dec02bbc965d5f08eac8dc8d.tar.xz mullvadvpn-c941655b88492ae7dec02bbc965d5f08eac8dc8d.zip | |
Merge branch 'let-users-cancel-sending-a-problem-report-ios-987'
15 files changed, 203 insertions, 87 deletions
diff --git a/ios/MullvadMockData/MullvadREST/MockRelayCache.swift b/ios/MullvadMockData/MullvadREST/MockRelayCache.swift index f2b0123b73..122a3e93ce 100644 --- a/ios/MullvadMockData/MullvadREST/MockRelayCache.swift +++ b/ios/MullvadMockData/MullvadREST/MockRelayCache.swift @@ -5,6 +5,7 @@ // Created by Mojgan on 2025-03-10. // Copyright © 2025 Mullvad VPN AB. All rights reserved. // + import Foundation @testable import MullvadREST diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj index eb7a32dcd8..2251f071e2 100644 --- a/ios/MullvadVPN.xcodeproj/project.pbxproj +++ b/ios/MullvadVPN.xcodeproj/project.pbxproj @@ -561,6 +561,7 @@ 7A6389ED2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389EC2B7FADA1008E77E1 /* SettingsFieldValidationErrorConfiguration.swift */; }; 7A6389F82B864CDF008E77E1 /* LocationNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6389F72B864CDF008E77E1 /* LocationNode.swift */; }; 7A6652B82BB44C3E0042D848 /* LocationDiffableDataSourceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6652B62BB44B120042D848 /* LocationDiffableDataSourceProtocol.swift */; }; + 7A6811542DC8EC6E009CB61A /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */; }; 7A6B4F592AB8412E00123853 /* TunnelMonitorTimings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6B4F582AB8412E00123853 /* TunnelMonitorTimings.swift */; }; 7A6F2FA52AFA3CB2006D0856 /* AccountExpiryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA42AFA3CB2006D0856 /* AccountExpiryTests.swift */; }; 7A6F2FA72AFBB9AE006D0856 /* AccountExpiry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A6F2FA62AFBB9AE006D0856 /* AccountExpiry.swift */; }; @@ -6015,6 +6016,7 @@ A9A5FA0D2ACB05160083449F /* StorePaymentManagerError.swift in Sources */, A9A5FA0E2ACB05160083449F /* StorePaymentObserver.swift in Sources */, A9A5FA0F2ACB05160083449F /* StoreSubscription.swift in Sources */, + 7A6811542DC8EC6E009CB61A /* UIFont+Weight.swift in Sources */, A9A5FA102ACB05160083449F /* PacketTunnelTransport.swift in Sources */, 7AD63A472CDA666100445268 /* UIntTests.swift in Sources */, A9A5FA112ACB05160083449F /* TransportMonitor.swift in Sources */, diff --git a/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift b/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift index 07811aec1b..5075cae646 100644 --- a/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift +++ b/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift @@ -11,9 +11,4 @@ import UIKit struct MarkdownStylingOptions { var font: UIFont var paragraphStyle: NSParagraphStyle = .default - - var boldFont: UIFont { - let fontDescriptor = font.fontDescriptor.withSymbolicTraits(.traitBold) ?? font.fontDescriptor - return UIFont(descriptor: fontDescriptor, size: font.pointSize) - } } diff --git a/ios/MullvadVPN/Extensions/NSAttributedString+Extensions.swift b/ios/MullvadVPN/Extensions/NSAttributedString+Extensions.swift index 9e5745a56f..fd9ebc5e7e 100644 --- a/ios/MullvadVPN/Extensions/NSAttributedString+Extensions.swift +++ b/ios/MullvadVPN/Extensions/NSAttributedString+Extensions.swift @@ -27,7 +27,7 @@ extension NSAttributedString { if stringIndex % 2 == 0 { attributes[.font] = options.font } else { - attributes[.font] = options.boldFont + attributes[.font] = options.font.withWeight(.bold) attributes.merge(applyEffect?(.bold, string) ?? [:], uniquingKeysWith: { $1 }) } diff --git a/ios/MullvadVPN/Extensions/UIFont+Weight.swift b/ios/MullvadVPN/Extensions/UIFont+Weight.swift index c3438650eb..146cbd128b 100644 --- a/ios/MullvadVPN/Extensions/UIFont+Weight.swift +++ b/ios/MullvadVPN/Extensions/UIFont+Weight.swift @@ -17,4 +17,13 @@ extension UIFont { return UIFont(descriptor: descriptor, size: 0) } + + func withWeight(_ weight: UIFont.Weight) -> UIFont { + let newDescriptor = fontDescriptor.addingAttributes([ + .traits: [ + UIFontDescriptor.TraitKey.weight: weight, + ], + ]) + return UIFont(descriptor: newDescriptor, size: pointSize) + } } diff --git a/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift index 59441d899d..02991b5848 100644 --- a/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift @@ -60,12 +60,12 @@ class LatestChangesNotificationProvider: NotificationProvider, InAppNotification value: "**Tap here** to see what’s new.", comment: "" ), - options: MarkdownStylingOptions(font: UIFont.preferredFont(forTextStyle: .body)), - applyEffect: { markdownType, _ in - guard case .bold = markdownType else { return [:] } - return [.foregroundColor: UIColor.InAppNotificationBanner.titleColor] - } - ) + options: MarkdownStylingOptions( + font: .preferredFont(forTextStyle: .body) + ) + ) { _, _ in + [.foregroundColor: UIColor.InAppNotificationBanner.titleColor] + } } private func createCloseButtonAction() -> InAppNotificationAction { diff --git a/ios/MullvadVPN/Notifications/Notification Providers/NewDeviceNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/NewDeviceNotificationProvider.swift index 22a7a09da5..eb6b9ef32f 100644 --- a/ios/MullvadVPN/Notifications/Notification Providers/NewDeviceNotificationProvider.swift +++ b/ios/MullvadVPN/Notifications/Notification Providers/NewDeviceNotificationProvider.swift @@ -33,14 +33,13 @@ final class NewDeviceNotificationProvider: NotificationProvider, let deviceName = storedDeviceData?.capitalizedName ?? "" let string = String(format: formattedString, deviceName) - let stylingOptions = MarkdownStylingOptions(font: .systemFont(ofSize: 14.0)) - - return NSAttributedString(markdownString: string, options: stylingOptions) { markdownType, _ in - if case .bold = markdownType { - return [.foregroundColor: UIColor.InAppNotificationBanner.titleColor] - } else { - return [:] - } + return NSAttributedString( + markdownString: string, + options: MarkdownStylingOptions( + font: .preferredFont(forTextStyle: .subheadline) + ) + ) { _, _ in + [.foregroundColor: UIColor.InAppNotificationBanner.titleColor] } } diff --git a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift index 190e1e7281..521b5bc95f 100644 --- a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift +++ b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift @@ -328,9 +328,7 @@ class AccountDeletionContentView: UIView { ) messageLabel.attributedText = NSAttributedString( markdownString: text, - options: MarkdownStylingOptions( - font: .preferredFont(forTextStyle: .body) - ) + options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body)) ) } } diff --git a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift index 8663d8bdb3..b297401213 100644 --- a/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift +++ b/ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift @@ -200,9 +200,7 @@ class DeviceManagementViewController: UIViewController, RootContainment { let attributedText = NSAttributedString( markdownString: text, - options: MarkdownStylingOptions( - font: .preferredFont(forTextStyle: .body) - ) + options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body)) ) let presentation = AlertPresentation( diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift index ffdffee232..4e2bb87763 100644 --- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift +++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift @@ -147,7 +147,7 @@ class OutOfTimeContentView: UIView { func setBodyLabelText(_ text: String) { bodyLabel.attributedText = NSAttributedString( markdownString: text, - options: MarkdownStylingOptions(font: .systemFont(ofSize: 17)) + options: MarkdownStylingOptions(font: .preferredFont(forTextStyle: .body)) ) } } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift index 98746416dc..194e7271f8 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift @@ -16,6 +16,7 @@ final class ProblemReportInteractor: @unchecked Sendable { private let tunnelManager: TunnelManager private let consolidatedLog: ConsolidatedApplicationLog private var reportedString = "" + private var requestCancellable: Cancellable? init(apiProxy: APIQuerying, tunnelManager: TunnelManager) { self.apiProxy = apiProxy @@ -63,6 +64,10 @@ final class ProblemReportInteractor: @unchecked Sendable { } } + func cancelSendingReport() { + requestCancellable?.cancel() + } + private func sendProblemReport( email: String, message: String, @@ -80,10 +85,10 @@ final class ProblemReportInteractor: @unchecked Sendable { metadata: metadataDict ) - _ = self.apiProxy.sendProblemReport(request, retryStrategy: .default, completionHandler: { result in + requestCancellable = apiProxy.sendProblemReport(request, retryStrategy: .default) { result in DispatchQueue.main.async { completion(result) } - }) + } } } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift index e4b68a4cea..005a81bd64 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift @@ -11,6 +11,8 @@ import MullvadREST import UIKit class ProblemReportSubmissionOverlayView: UIView { + var viewLogsButtonAction: (() -> Void)? + var cancelButtonAction: (() -> Void)? var editButtonAction: (() -> Void)? var retryButtonAction: (() -> Void)? @@ -19,24 +21,28 @@ class ProblemReportSubmissionOverlayView: UIView { case sent(_ email: String) case failure(Error) + var supportEmail: String { + "support@mullvadvpn.net" + } + var title: String? { switch self { case .sending: - return NSLocalizedString( + NSLocalizedString( "SUBMISSION_STATUS_SENDING", tableName: "ProblemReport", value: "Sending...", comment: "" ) case .sent: - return NSLocalizedString( + NSLocalizedString( "SUBMISSION_STATUS_SENT", tableName: "ProblemReport", value: "Sent", comment: "" ) case .failure: - return NSLocalizedString( + NSLocalizedString( "SUBMISSION_STATUS_FAILURE", tableName: "ProblemReport", value: "Failed to send", @@ -45,7 +51,7 @@ class ProblemReportSubmissionOverlayView: UIView { } } - var body: NSAttributedString? { + var body: [NSAttributedString]? { switch self { case .sending: return nil @@ -56,8 +62,7 @@ class ProblemReportSubmissionOverlayView: UIView { tableName: "ProblemReport", value: "Thanks!", comment: "" - ), - attributes: [.foregroundColor: UIColor.successColor] + ) ) if email.isEmpty { @@ -93,14 +98,44 @@ class ProblemReportSubmissionOverlayView: UIView { combinedAttributedString.append(emailAttributedString) } - return combinedAttributedString + return [combinedAttributedString] - case let .failure(error): - if let error = error as? REST.Error { - return error.displayErrorDescription.flatMap { NSAttributedString(string: $0) } - } else { - return NSAttributedString(string: error.localizedDescription) - } + case .failure: + return [ + NSAttributedString( + string: NSLocalizedString( + "MESSAGE_FAILED_PART_1", + tableName: "ProblemReport", + value: + """ + If you exit the form and try again later, the information you already entered will still \ + be here. + """, + comment: "" + ) + ), + NSAttributedString( + markdownString: NSLocalizedString( + "MESSAGE_FAILED_PART_2", + tableName: "ProblemReport", + value: + """ + If you still experience issues you can email our support directly at \ + **\(supportEmail)**. Please attach your app log to your email. + """, + comment: "" + ), + options: MarkdownStylingOptions( + font: .preferredFont(forTextStyle: .body) + ), applyEffect: { _, _ in + [ + // Setting font again to circumvent bold weight. + .font: UIFont.preferredFont(forTextStyle: .body), + .foregroundColor: UIColor.white, + ] + } + ), + ] } } } @@ -127,24 +162,54 @@ class ProblemReportSubmissionOverlayView: UIView { return textLabel }() - let bodyLabel: UILabel = { - let textLabel = UILabel() - textLabel.font = UIFont.systemFont(ofSize: 17) - textLabel.textColor = .white - textLabel.numberOfLines = 0 - return textLabel + let bodyLabelContainer: UIStackView = { + let stackView = UIStackView() + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 24 + return stackView }() - /// Footer stack view that contains action buttons - private lazy var buttonsStackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [self.editMessageButton, self.tryAgainButton]) + /// Footer stack view that contains action buttons. + private lazy var buttonContainer: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [cancelButton, failedToSendButtons]) stackView.translatesAutoresizingMaskIntoConstraints = false stackView.axis = .vertical stackView.spacing = 18 + return stackView + }() + /// Footer stack view that contains action buttons when sending failed. + private lazy var failedToSendButtons: UIStackView = { + let stackView = UIStackView(arrangedSubviews: [editMessageButton, viewLogsButton, tryAgainButton]) + stackView.translatesAutoresizingMaskIntoConstraints = false + stackView.axis = .vertical + stackView.spacing = 18 return stackView }() + private lazy var viewLogsButton: AppButton = { + let button = AppButton(style: .default) + button.setAccessibilityIdentifier(.problemReportAppLogsButton) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(ProblemReportViewModel.viewLogsButtonTitle, for: .normal) + button.addTarget(self, action: #selector(handleViewLogsButton), for: .touchUpInside) + return button + }() + + private lazy var cancelButton: AppButton = { + let button = AppButton(style: .default) + button.translatesAutoresizingMaskIntoConstraints = false + button.setTitle(NSLocalizedString( + "CANCEL_BUTTON", + tableName: "ProblemReport", + value: "Cancel", + comment: "" + ), for: .normal) + button.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside) + return button + }() + private lazy var editMessageButton: AppButton = { let button = AppButton(style: .default) button.translatesAutoresizingMaskIntoConstraints = false @@ -189,10 +254,10 @@ class ProblemReportSubmissionOverlayView: UIView { private func addSubviews() { for subview in [ titleLabel, - bodyLabel, + bodyLabelContainer, activityIndicator, statusImageView, - buttonsStackView, + buttonContainer, ] { subview.translatesAutoresizingMaskIntoConstraints = false addSubview(subview) @@ -212,49 +277,81 @@ class ProblemReportSubmissionOverlayView: UIView { titleLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), titleLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - bodyLabel.topAnchor.constraint( + bodyLabelContainer.topAnchor.constraint( equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1 ), - bodyLabel.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - bodyLabel.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonsStackView.topAnchor.constraint( - greaterThanOrEqualTo: bodyLabel.bottomAnchor, + bodyLabelContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + bodyLabelContainer.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + buttonContainer.topAnchor.constraint( + greaterThanOrEqualTo: bodyLabelContainer.bottomAnchor, constant: 18 ), - buttonsStackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), - buttonsStackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), - buttonsStackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), + buttonContainer.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor), + buttonContainer.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor), + buttonContainer.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor), ]) } private func transitionToState(_ state: State) { titleLabel.text = state.title - bodyLabel.attributedText = state.body + + bodyLabelContainer.subviews.forEach { $0.removeFromSuperview() } + state.body?.forEach { attributedString in + let textLabel = UILabel() + textLabel.font = UIFont.systemFont(ofSize: 17) + textLabel.textColor = .white.withAlphaComponent(0.6) + textLabel.numberOfLines = 0 + textLabel.attributedText = attributedString + + if attributedString.string.contains(state.supportEmail) { + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleEmailLabelTap)) + textLabel.addGestureRecognizer(tapGesture) + textLabel.isUserInteractionEnabled = true + } + + bodyLabelContainer.addArrangedSubview(textLabel) + } switch state { case .sending: activityIndicator.startAnimating() statusImageView.isHidden = true - buttonsStackView.isHidden = true + cancelButton.isHidden = false + failedToSendButtons.isHidden = true case .sent: activityIndicator.stopAnimating() statusImageView.style = .success statusImageView.isHidden = false - buttonsStackView.isHidden = true + buttonContainer.isHidden = true case .failure: activityIndicator.stopAnimating() statusImageView.style = .failure statusImageView.isHidden = false - buttonsStackView.isHidden = false + cancelButton.isHidden = true + failedToSendButtons.isHidden = false } } // MARK: - Actions + @objc private func handleEmailLabelTap() { + if let url = URL(string: "mailto:\(state.supportEmail)") { + UIApplication.shared.open(url) + } + } + + @objc private func handleViewLogsButton() { + viewLogsButtonAction?() + } + + @objc private func handleCancelButton() { + cancelButtonAction?() + } + @objc private func handleEditButton() { editButtonAction?() } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController+ViewManagement.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController+ViewManagement.swift index 484c60ef8f..c5370188eb 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController+ViewManagement.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController+ViewManagement.swift @@ -30,7 +30,7 @@ extension ProblemReportViewController { textLabel.translatesAutoresizingMaskIntoConstraints = false textLabel.numberOfLines = 0 textLabel.textColor = .white - textLabel.text = Self.persistentViewModel.subheadLabelText + textLabel.text = ProblemReportViewModel.subheadLabelText return textLabel } @@ -48,7 +48,7 @@ extension ProblemReportViewController { textField.backgroundColor = .white textField.inputAccessoryView = emailAccessoryToolbar textField.font = UIFont.systemFont(ofSize: 17) - textField.placeholder = Self.persistentViewModel.emailPlaceholderText + textField.placeholder = ProblemReportViewModel.emailPlaceholderText return textField } @@ -58,7 +58,7 @@ extension ProblemReportViewController { textView.backgroundColor = .white textView.inputAccessoryView = messageAccessoryToolbar textView.font = UIFont.systemFont(ofSize: 17) - textView.placeholder = Self.persistentViewModel.messageTextViewPlaceholder + textView.placeholder = ProblemReportViewModel.messageTextViewPlaceholder textView.contentInsetAdjustmentBehavior = .never return textView @@ -90,7 +90,7 @@ extension ProblemReportViewController { let button = AppButton(style: .default) button.setAccessibilityIdentifier(.problemReportAppLogsButton) button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle(Self.persistentViewModel.viewLogsButtonTitle, for: .normal) + button.setTitle(ProblemReportViewModel.viewLogsButtonTitle, for: .normal) button.addTarget(self, action: #selector(handleViewLogsButtonTap), for: .touchUpInside) return button } @@ -99,7 +99,7 @@ extension ProblemReportViewController { let button = AppButton(style: .success) button.setAccessibilityIdentifier(.problemReportSendButton) button.translatesAutoresizingMaskIntoConstraints = false - button.setTitle(Self.persistentViewModel.sendLogsButtonTitle, for: .normal) + button.setTitle(ProblemReportViewModel.sendLogsButtonTitle, for: .normal) button.addTarget(self, action: #selector(handleSendButtonTap), for: .touchUpInside) return button } @@ -108,6 +108,14 @@ extension ProblemReportViewController { let overlay = ProblemReportSubmissionOverlayView() overlay.translatesAutoresizingMaskIntoConstraints = false + overlay.viewLogsButtonAction = { [weak self] in + self?.handleViewLogsButtonTap() + } + + overlay.cancelButtonAction = { [weak self] in + self?.interactor.cancelSendingReport() + } + overlay.editButtonAction = { [weak self] in self?.hideSubmissionOverlay() } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift index 0e7cda5f44..e1b37598cb 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift @@ -12,8 +12,8 @@ import Operations import UIKit final class ProblemReportViewController: UIViewController, UITextFieldDelegate { - private let interactor: ProblemReportInteractor private let alertPresenter: AlertPresenter + let interactor: ProblemReportInteractor var textViewKeyboardResponder: AutomaticKeyboardResponder? var scrollViewKeyboardResponder: AutomaticKeyboardResponder? @@ -77,7 +77,7 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate { view.backgroundColor = .secondaryColor view.setAccessibilityIdentifier(.problemReportView) - navigationItem.title = Self.persistentViewModel.navigationTitle + navigationItem.title = ProblemReportViewModel.navigationTitle textViewKeyboardResponder = AutomaticKeyboardResponder(targetView: messageTextView) scrollViewKeyboardResponder = AutomaticKeyboardResponder(targetView: scrollView) @@ -170,17 +170,17 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate { let presentation = AlertPresentation( id: "problem-report-alert", icon: .alert, - message: Self.persistentViewModel.emptyEmailAlertWarning, + message: ProblemReportViewModel.emptyEmailAlertWarning, buttons: [ AlertAction( - title: Self.persistentViewModel.confirmEmptyEmailTitle, + title: ProblemReportViewModel.confirmEmptyEmailTitle, style: .destructive, handler: { completion(true) } ), AlertAction( - title: Self.persistentViewModel.cancelEmptyEmailTitle, + title: ProblemReportViewModel.cancelEmptyEmailTitle, style: .default, handler: { completion(false) @@ -245,7 +245,11 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate { clearPersistentViewModel() case let .failure(error): - submissionOverlayView.state = .failure(error) + if let error = error as? OperationError, error == .cancelled { + hideSubmissionOverlay() + } else { + submissionOverlayView.state = .failure(error) + } } navigationItem.setHidesBackButton(false, animated: true) @@ -261,9 +265,9 @@ final class ProblemReportViewController: UIViewController, UITextFieldDelegate { interactor.sendReport( email: viewModel.email, message: viewModel.message - ) { completion in + ) { [weak self] completion in Task { @MainActor in - self.didSendProblemReport(viewModel: viewModel, completion: completion) + self?.didSendProblemReport(viewModel: viewModel, completion: completion) } } } diff --git a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift index 6805b97b6c..e6537ec46b 100644 --- a/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift +++ b/ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift @@ -12,14 +12,14 @@ struct ProblemReportViewModel { let email: String let message: String - let navigationTitle = NSLocalizedString( + static let navigationTitle = NSLocalizedString( "NAVIGATION_TITLE", tableName: "ProblemReport", value: "Report a problem", comment: "" ) - let subheadLabelText = NSLocalizedString( + static let subheadLabelText = NSLocalizedString( "SUBHEAD_LABEL", tableName: "ProblemReport", value: """ @@ -30,14 +30,14 @@ struct ProblemReportViewModel { comment: "" ) - let emailPlaceholderText = NSLocalizedString( + static let emailPlaceholderText = NSLocalizedString( "EMAIL_TEXTFIELD_PLACEHOLDER", tableName: "ProblemReport", value: "Your email (optional)", comment: "" ) - let messageTextViewPlaceholder = NSLocalizedString( + static let messageTextViewPlaceholder = NSLocalizedString( "DESCRIPTION_TEXTVIEW_PLACEHOLDER", tableName: "ProblemReport", value: """ @@ -47,21 +47,21 @@ struct ProblemReportViewModel { comment: "" ) - let viewLogsButtonTitle = NSLocalizedString( + static let viewLogsButtonTitle = NSLocalizedString( "VIEW_APP_LOGS_BUTTON_TITLE", tableName: "ProblemReport", value: "View app logs", comment: "" ) - let sendLogsButtonTitle = NSLocalizedString( + static let sendLogsButtonTitle = NSLocalizedString( "SEND_BUTTON_TITLE", tableName: "ProblemReport", value: "Send", comment: "" ) - let emptyEmailAlertWarning = NSLocalizedString( + static let emptyEmailAlertWarning = NSLocalizedString( "EMPTY_EMAIL_ALERT_MESSAGE", tableName: "ProblemReport", value: """ @@ -71,14 +71,14 @@ struct ProblemReportViewModel { comment: "" ) - let confirmEmptyEmailTitle = NSLocalizedString( + static let confirmEmptyEmailTitle = NSLocalizedString( "EMPTY_EMAIL_ALERT_SEND_ANYWAY_ACTION", tableName: "ProblemReport", value: "Send anyway", comment: "" ) - let cancelEmptyEmailTitle = NSLocalizedString( + static let cancelEmptyEmailTitle = NSLocalizedString( "EMPTY_EMAIL_ALERT_CANCEL_ACTION", tableName: "ProblemReport", value: "Cancel", |
