summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@mullvad.net>2025-05-30 11:34:42 +0200
committerJon Petersson <jon.petersson@mullvad.net>2025-05-30 11:34:42 +0200
commitc941655b88492ae7dec02bbc965d5f08eac8dc8d (patch)
treeec3f39acc4e1cc54ad9392d30a167abcae1d1f23
parent42b53b4bcd3d3155d42ebca7e33c9450ca5dab73 (diff)
parent8b77bdbdf51a99b9b833bc56a8d67fdeb46fc581 (diff)
downloadmullvadvpn-c941655b88492ae7dec02bbc965d5f08eac8dc8d.tar.xz
mullvadvpn-c941655b88492ae7dec02bbc965d5f08eac8dc8d.zip
Merge branch 'let-users-cancel-sending-a-problem-report-ios-987'
-rw-r--r--ios/MullvadMockData/MullvadREST/MockRelayCache.swift1
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj2
-rw-r--r--ios/MullvadVPN/Classes/MarkdownStylingOptions.swift5
-rw-r--r--ios/MullvadVPN/Extensions/NSAttributedString+Extensions.swift2
-rw-r--r--ios/MullvadVPN/Extensions/UIFont+Weight.swift9
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/LatestChangesNotificationProvider.swift12
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/NewDeviceNotificationProvider.swift15
-rw-r--r--ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift4
-rw-r--r--ios/MullvadVPN/View controllers/DeviceList/DeviceManagementViewController.swift4
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift2
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportInteractor.swift9
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportSubmissionOverlayView.swift169
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController+ViewManagement.swift18
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewController.swift20
-rw-r--r--ios/MullvadVPN/View controllers/ProblemReport/ProblemReportViewModel.swift18
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",