summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJon Petersson <jon.petersson@kvadrat.se>2023-06-22 16:47:22 +0200
committerBug Magnet <marco.nikic@mullvad.net>2023-08-10 11:50:39 +0200
commitfd1bfc1c7d3b1ff4614a4bd6047f89071891adbe (patch)
tree4666deb88fb7a0467625b38a5585475e4ad3510c
parent5b5a4a68b044729ae9d7f1f8247ed35dbb4e8863 (diff)
downloadmullvadvpn-fd1bfc1c7d3b1ff4614a4bd6047f89071891adbe.tar.xz
mullvadvpn-fd1bfc1c7d3b1ff4614a4bd6047f89071891adbe.zip
Rework change log to be compliant with our design
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj19
-rw-r--r--ios/MullvadVPN/Classes/CustomAlertViewController.swift78
-rw-r--r--ios/MullvadVPN/Classes/MarkdownStylingOptions.swift19
-rw-r--r--ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift28
-rw-r--r--ios/MullvadVPN/Coordinators/App/ChangeLogCoordinator.swift73
-rw-r--r--ios/MullvadVPN/Extensions/NSAttributedString+Markdown.swift10
-rw-r--r--ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift2
-rw-r--r--ios/MullvadVPN/UI appearance/UIMetrics.swift3
-rw-r--r--ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift7
-rw-r--r--ios/MullvadVPN/View controllers/ChangeLog/ChangeLogContentView.swift144
-rw-r--r--ios/MullvadVPN/View controllers/ChangeLog/ChangeLogViewController.swift53
-rw-r--r--ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift2
-rw-r--r--ios/MullvadVPN/View controllers/Preferences/PreferencesViewModel.swift2
13 files changed, 161 insertions, 279 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index d71fb082c5..6614c287fc 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -160,10 +160,8 @@
5878A279290954790096FC88 /* TunnelViewControllerInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */; };
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */; };
5878A27D2909657C0096FC88 /* RevokedDeviceInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878A27C2909657C0096FC88 /* RevokedDeviceInteractor.swift */; };
- 5878F4FC29CDA2E4003D4BE2 /* ChangeLogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F4FB29CDA2E4003D4BE2 /* ChangeLogViewController.swift */; };
5878F50029CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */; };
5878F50229CDB989003D4BE2 /* ChangeLogCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F50129CDB989003D4BE2 /* ChangeLogCoordinator.swift */; };
- 5878F50429CDC547003D4BE2 /* ChangeLogContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F50329CDC547003D4BE2 /* ChangeLogContentView.swift */; };
587988C728A2A01F00E3DF54 /* AccountDataThrottling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */; };
587A01FC23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */; };
587AD7C623421D7000E93A53 /* TunnelSettingsV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */; };
@@ -396,6 +394,7 @@
7ABE318D2A1CDD4500DF4963 /* UIFont+Weight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */; };
7AE47E522A17972A000418DA /* CustomAlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE47E512A17972A000418DA /* CustomAlertViewController.swift */; };
7AF0419E29E957EB00D492DD /* AccountCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */; };
+ 7AF9BE992A4E0FE900DBFEDB /* MarkdownStylingOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */; };
A917351F29FAA9C400D5DCFD /* RESTTransportStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */; };
A917352129FAAA5200D5DCFD /* TransportStrategyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */; };
A92ECC212A77FFAF0052F1B1 /* TunnelSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = A92ECC202A77FFAF0052F1B1 /* TunnelSettings.swift */; };
@@ -1012,10 +1011,8 @@
5878A278290954790096FC88 /* TunnelViewControllerInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelViewControllerInteractor.swift; sourceTree = "<group>"; };
5878A27A2909649A0096FC88 /* CustomOverlayRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomOverlayRenderer.swift; sourceTree = "<group>"; };
5878A27C2909657C0096FC88 /* RevokedDeviceInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevokedDeviceInteractor.swift; sourceTree = "<group>"; };
- 5878F4FB29CDA2E4003D4BE2 /* ChangeLogViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogViewController.swift; sourceTree = "<group>"; };
5878F4FF29CDA742003D4BE2 /* UIView+AutoLayoutBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+AutoLayoutBuilder.swift"; sourceTree = "<group>"; };
5878F50129CDB989003D4BE2 /* ChangeLogCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogCoordinator.swift; sourceTree = "<group>"; };
- 5878F50329CDC547003D4BE2 /* ChangeLogContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChangeLogContentView.swift; sourceTree = "<group>"; };
587988C628A2A01F00E3DF54 /* AccountDataThrottling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDataThrottling.swift; sourceTree = "<group>"; };
587A01FB23F1F0BE00B68763 /* SimulatorTunnelProviderHost.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimulatorTunnelProviderHost.swift; sourceTree = "<group>"; };
587AD7C523421D7000E93A53 /* TunnelSettingsV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TunnelSettingsV1.swift; sourceTree = "<group>"; };
@@ -1198,6 +1195,7 @@
7ABE318C2A1CDD4500DF4963 /* UIFont+Weight.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIFont+Weight.swift"; sourceTree = "<group>"; };
7AE47E512A17972A000418DA /* CustomAlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomAlertViewController.swift; sourceTree = "<group>"; };
7AF0419D29E957EB00D492DD /* AccountCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountCoordinator.swift; sourceTree = "<group>"; };
+ 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownStylingOptions.swift; sourceTree = "<group>"; };
A917351E29FAA9C400D5DCFD /* RESTTransportStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RESTTransportStrategy.swift; sourceTree = "<group>"; };
A917352029FAAA5200D5DCFD /* TransportStrategyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransportStrategyTests.swift; sourceTree = "<group>"; };
A92ECC202A77FFAF0052F1B1 /* TunnelSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TunnelSettings.swift; sourceTree = "<group>"; };
@@ -1809,6 +1807,7 @@
58138E60294871C600684F0C /* DeviceDataThrottling.swift */,
7A7AD28C29DC677800480EF1 /* FirstTimeLaunch.swift */,
582AE30F2440A6CA00E6733A /* InputTextFormatter.swift */,
+ 7AF9BE982A4E0FE900DBFEDB /* MarkdownStylingOptions.swift */,
58CC40EE24A601900019D96E /* ObserverList.swift */,
5872D6E7286304DE00DB5F4E /* TermsOfService.swift */,
);
@@ -1919,15 +1918,6 @@
path = Operations;
sourceTree = "<group>";
};
- 5878F4FA29CDA2D4003D4BE2 /* ChangeLog */ = {
- isa = PBXGroup;
- children = (
- 5878F4FB29CDA2E4003D4BE2 /* ChangeLogViewController.swift */,
- 5878F50329CDC547003D4BE2 /* ChangeLogContentView.swift */,
- );
- path = ChangeLog;
- sourceTree = "<group>";
- };
587B75422669034500DEF7E9 /* Notification Providers */ = {
isa = PBXGroup;
children = (
@@ -3268,13 +3258,13 @@
5878A27B2909649A0096FC88 /* CustomOverlayRenderer.swift in Sources */,
5847D58D29B7740F008C3808 /* RevokedCoordinator.swift in Sources */,
588527B2276B3F0700BAA373 /* LoadTunnelConfigurationOperation.swift in Sources */,
+ 7AF9BE992A4E0FE900DBFEDB /* MarkdownStylingOptions.swift in Sources */,
5867770E29096984006F721F /* OutOfTimeInteractor.swift in Sources */,
58F185AA298A3E3E00075977 /* TunnelCoordinator.swift in Sources */,
F03580252A13842C00E5DAFD /* IncreasedHitButton.swift in Sources */,
58F8AC0E25D3F8CE002BE0ED /* ProblemReportReviewViewController.swift in Sources */,
F028A56E2A34DCC600C0CAA3 /* RedeemVoucherInteractor.swift in Sources */,
5878A27129091CF20096FC88 /* AccountInteractor.swift in Sources */,
- 5878F4FC29CDA2E4003D4BE2 /* ChangeLogViewController.swift in Sources */,
068CE5742927B7A400A068BB /* Migration.swift in Sources */,
A92ECC282A7802AB0052F1B1 /* StoredDeviceData.swift in Sources */,
58CCA010224249A1004F3011 /* TunnelViewController.swift in Sources */,
@@ -3419,7 +3409,6 @@
587D9676288989DB00CD8F1C /* NSLayoutConstraint+Helpers.swift in Sources */,
F028A56C2A34D8E600C0CAA3 /* RedeemVoucherSucceededViewController.swift in Sources */,
58293FAE2510CA58005D0BB5 /* ProblemReportViewController.swift in Sources */,
- 5878F50429CDC547003D4BE2 /* ChangeLogContentView.swift in Sources */,
F028A5492A336E8500C0CAA3 /* VoucherTextField.swift in Sources */,
58B9EB152489139B00095626 /* RESTError+Display.swift in Sources */,
587B753F2668E5A700DEF7E9 /* NotificationContainerView.swift in Sources */,
diff --git a/ios/MullvadVPN/Classes/CustomAlertViewController.swift b/ios/MullvadVPN/Classes/CustomAlertViewController.swift
index 0720785419..cb6b92d1df 100644
--- a/ios/MullvadVPN/Classes/CustomAlertViewController.swift
+++ b/ios/MullvadVPN/Classes/CustomAlertViewController.swift
@@ -16,7 +16,7 @@ class CustomAlertViewController: UIViewController {
case info
case spinner
- var image: UIImage? {
+ fileprivate var image: UIImage? {
switch self {
case .alert:
return UIImage(named: "IconAlert")?.withTintColor(.dangerColor)
@@ -59,38 +59,65 @@ class CustomAlertViewController: UIViewController {
private var handlers = [UIButton: Handler]()
- init(title: String? = nil, message: String? = nil, icon: Icon? = nil) {
+ init(header: String? = nil, title: String? = nil, message: String? = nil, icon: Icon? = nil) {
super.init(nibName: nil, bundle: nil)
+ setUp(header: header, title: title, icon: icon) {
+ message.flatMap { addMessage($0) }
+ }
+ }
+
+ init(header: String? = nil, title: String? = nil, attributedMessage: NSAttributedString?, icon: Icon? = nil) {
+ super.init(nibName: nil, bundle: nil)
+
+ setUp(header: header, title: title, icon: icon) {
+ attributedMessage.flatMap { addMessage($0) }
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ // This code runs before viewDidLoad(). As such, no implicit calls to self.view should be made before this point.
+ private func setUp(header: String?, title: String?, icon: Icon?, addMessageCallback: () -> Void) {
modalPresentationStyle = .overFullScreen
modalTransitionStyle = .crossDissolve
icon.flatMap { addIcon($0) }
+ header.flatMap { addHeader($0) }
title.flatMap { addTitle($0) }
- message.flatMap { addMessage($0) }
+ addMessageCallback()
containerView.arrangedSubviews.last.flatMap {
containerView.setCustomSpacing(UIMetrics.CustomAlert.containerMargins.top, after: $0)
}
// Icon only alerts should have equal top and bottom margin.
- if title == nil, message == nil {
+ if icon != nil, containerView.arrangedSubviews.count == 1 {
containerView.directionalLayoutMargins.bottom = UIMetrics.CustomAlert.containerMargins.top
}
}
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .black.withAlphaComponent(0.5)
view.addConstrainedSubviews([containerView]) {
- containerView.pinEdges(.init([.leading(0), .trailing(0)]), to: view.layoutMarginsGuide)
+ containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor)
containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor)
+
+ containerView.widthAnchor
+ .constraint(lessThanOrEqualToConstant: UIMetrics.preferredFormSheetContentSize.width)
+
+ containerView.leadingAnchor
+ .constraint(equalTo: view.layoutMarginsGuide.leadingAnchor)
+ .withPriority(.defaultHigh)
+
+ view.layoutMarginsGuide.trailingAnchor
+ .constraint(equalTo: containerView.trailingAnchor)
+ .withPriority(.defaultHigh)
}
}
@@ -107,29 +134,46 @@ class CustomAlertViewController: UIViewController {
handler.flatMap { handlers[button] = $0 }
}
+ private func addHeader(_ title: String) {
+ let header = UILabel()
+
+ header.text = title
+ header.font = .preferredFont(forTextStyle: .largeTitle, weight: .bold)
+ header.textColor = .white
+ header.adjustsFontForContentSizeCategory = true
+ header.textAlignment = .center
+ header.numberOfLines = 0
+
+ containerView.addArrangedSubview(header)
+ containerView.setCustomSpacing(16, after: header)
+ }
+
private func addTitle(_ title: String) {
let label = UILabel()
label.text = title
label.font = .preferredFont(forTextStyle: .title3, weight: .semibold)
- label.textColor = .white
+ label.textColor = .white.withAlphaComponent(0.9)
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
containerView.addArrangedSubview(label)
+ containerView.setCustomSpacing(8, after: label)
}
private func addMessage(_ message: String) {
let label = UILabel()
+ let font = UIFont.preferredFont(forTextStyle: .body)
let style = NSMutableParagraphStyle()
style.paragraphSpacing = 16
+ style.lineBreakMode = .byWordWrapping
label.attributedText = NSAttributedString(
markdownString: message,
- options: .init(font: .preferredFont(forTextStyle: .body), paragraphStyle: style)
+ options: MarkdownStylingOptions(font: font, paragraphStyle: style)
)
- label.font = .preferredFont(forTextStyle: .body)
+ label.font = font
label.textColor = .white.withAlphaComponent(0.8)
label.adjustsFontForContentSizeCategory = true
label.numberOfLines = 0
@@ -137,6 +181,16 @@ class CustomAlertViewController: UIViewController {
containerView.addArrangedSubview(label)
}
+ private func addMessage(_ message: NSAttributedString) {
+ let label = UILabel()
+
+ label.attributedText = message
+ label.adjustsFontForContentSizeCategory = true
+ label.numberOfLines = 0
+
+ containerView.addArrangedSubview(label)
+ }
+
private func addIcon(_ icon: Icon) {
let iconView = icon == .spinner ? getSpinnerView() : getImageView(for: icon)
containerView.addArrangedSubview(iconView)
diff --git a/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift b/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift
new file mode 100644
index 0000000000..1b2e2390d7
--- /dev/null
+++ b/ios/MullvadVPN/Classes/MarkdownStylingOptions.swift
@@ -0,0 +1,19 @@
+//
+// MarkdownStylingOptions.swift
+// MullvadVPN
+//
+// Created by Jon Petersson on 2023-06-29.
+// Copyright © 2023 Mullvad VPN AB. All rights reserved.
+//
+
+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/Coordinators/App/ApplicationCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
index ce421879e9..848dffe9af 100644
--- a/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/ApplicationCoordinator.swift
@@ -12,11 +12,6 @@ import RelayCache
import UIKit
/**
- Preferred content size for controllers presented using formsheet modal presentation style.
- */
-private let preferredFormSheetContentSize = CGSize(width: 480, height: 640)
-
-/**
Application coordinator managing split view and two navigation contexts.
*/
final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewControllerDelegate,
@@ -46,7 +41,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
private let secondaryNavigationContainer = RootContainerViewController()
private lazy var secondaryRootConfiguration = ModalPresentationConfiguration(
- preferredContentSize: preferredFormSheetContentSize,
+ preferredContentSize: UIMetrics.preferredFormSheetContentSize,
modalPresentationStyle: .custom,
isModalInPresentation: true,
transitioningDelegate: SecondaryContextTransitioningDelegate()
@@ -148,7 +143,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
presentLogin(animated: animated, completion: completion)
case .changelog:
- presentChangeLog(animated: animated, completion: completion)
+ presentChangeLog(completion: completion)
case .tos:
presentTOS(animated: animated, completion: completion)
@@ -497,19 +492,14 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
}
}
- private func presentChangeLog(animated: Bool, completion: @escaping (Coordinator) -> Void) {
- let coordinator = ChangeLogCoordinator(navigationController: horizontalFlowController)
-
- coordinator.didFinish = { [weak self] coordinator in
- self?.continueFlow(animated: true)
- }
+ private func presentChangeLog(completion: @escaping (Coordinator) -> Void) {
+ let coordinator = ChangeLogCoordinator(navigationController: primaryNavigationContainer)
addChild(coordinator)
- coordinator.start(animated: animated)
+ coordinator.start(animated: false)
- beginHorizontalFlow(animated: animated) {
- completion(coordinator)
- }
+ continueFlow(animated: false)
+ completion(coordinator)
}
private func presentMain(animated: Bool, completion: @escaping (Coordinator) -> Void) {
@@ -703,7 +693,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
coordinator,
animated: animated,
configuration: ModalPresentationConfiguration(
- preferredContentSize: preferredFormSheetContentSize,
+ preferredContentSize: UIMetrics.preferredFormSheetContentSize,
modalPresentationStyle: .formSheet
)
) { [weak self] in
@@ -747,7 +737,7 @@ final class ApplicationCoordinator: Coordinator, Presenting, RootContainerViewCo
coordinator,
animated: animated,
configuration: ModalPresentationConfiguration(
- preferredContentSize: preferredFormSheetContentSize,
+ preferredContentSize: UIMetrics.preferredFormSheetContentSize,
modalPresentationStyle: .formSheet
)
) {
diff --git a/ios/MullvadVPN/Coordinators/App/ChangeLogCoordinator.swift b/ios/MullvadVPN/Coordinators/App/ChangeLogCoordinator.swift
index feed4aa354..3b4f106205 100644
--- a/ios/MullvadVPN/Coordinators/App/ChangeLogCoordinator.swift
+++ b/ios/MullvadVPN/Coordinators/App/ChangeLogCoordinator.swift
@@ -11,36 +11,71 @@ import UIKit
final class ChangeLogCoordinator: Coordinator {
private let logger = Logger(label: "ChangeLogCoordinator")
+ private let navigationController: UIViewController
- let navigationController: RootContainerViewController
+ var presentedViewController: UIViewController {
+ return navigationController
+ }
+
+ private var changeLogText: NSAttributedString? {
+ guard let changeLogText = try? ChangeLog.readFromFile() else {
+ logger.error("Cannot read changelog from bundle.")
+ return nil
+ }
- var didFinish: ((ChangeLogCoordinator) -> Void)?
+ let bullet = "• "
+ let font = UIFont.preferredFont(forTextStyle: .body)
- init(navigationController: RootContainerViewController) {
+ let bulletList = changeLogText.split(whereSeparator: { $0.isNewline })
+ .map { "\(bullet)\($0)" }
+ .joined(separator: "\n")
+
+ let paragraphStyle = NSMutableParagraphStyle()
+ paragraphStyle.lineBreakMode = .byWordWrapping
+ paragraphStyle.headIndent = bullet.size(withAttributes: [.font: font]).width
+
+ return NSAttributedString(
+ string: bulletList,
+ attributes: [
+ .paragraphStyle: paragraphStyle,
+ .font: font,
+ .foregroundColor: UIColor.white.withAlphaComponent(0.8),
+ ]
+ )
+ }
+
+ init(navigationController: UIViewController) {
self.navigationController = navigationController
}
func start(animated: Bool) {
- let controller = ChangeLogViewController()
-
- controller.setApplicationVersion(Bundle.main.shortVersion)
-
- do {
- let string = try ChangeLog.readFromFile()
+ ChangeLog.markAsSeen()
- controller.setChangeLogText(string)
- } catch {
- logger.error(error: error, message: "Cannot read changelog from bundle.")
+ guard let changeLogText else {
+ return
}
- controller.onFinish = { [weak self] in
- guard let self else { return }
+ let alertController = CustomAlertViewController(
+ header: Bundle.main.shortVersion,
+ title: NSLocalizedString(
+ "CHANGE_LOG_TITLE",
+ tableName: "Account",
+ value: "Changes in this version:",
+ comment: ""
+ ),
+ attributedMessage: changeLogText
+ )
- ChangeLog.markAsSeen()
-
- didFinish?(self)
- }
+ alertController.addAction(
+ title: NSLocalizedString(
+ "CHANGE_LOG_OK_ACTION",
+ tableName: "Account",
+ value: "Got it!",
+ comment: ""
+ ),
+ style: .default
+ )
- navigationController.pushViewController(controller, animated: animated)
+ presentedViewController.present(alertController, animated: animated)
}
}
diff --git a/ios/MullvadVPN/Extensions/NSAttributedString+Markdown.swift b/ios/MullvadVPN/Extensions/NSAttributedString+Markdown.swift
index d273e2ad00..173888f606 100644
--- a/ios/MullvadVPN/Extensions/NSAttributedString+Markdown.swift
+++ b/ios/MullvadVPN/Extensions/NSAttributedString+Markdown.swift
@@ -13,16 +13,6 @@ extension NSAttributedString {
case bold
}
- 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)
- }
- }
-
convenience init(
markdownString: String,
options: MarkdownStylingOptions,
diff --git a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
index b00b9f0834..bd16fc41d0 100644
--- a/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
+++ b/ios/MullvadVPN/Notifications/Notification Providers/RegisteredDeviceInAppNotificationProvider.swift
@@ -32,7 +32,7 @@ final class RegisteredDeviceInAppNotificationProvider: NotificationProvider,
let deviceName = storedDeviceData?.capitalizedName ?? ""
let string = String(format: formattedString, deviceName)
- let stylingOptions = NSAttributedString.MarkdownStylingOptions(font: .systemFont(ofSize: 14.0))
+ let stylingOptions = MarkdownStylingOptions(font: .systemFont(ofSize: 14.0))
return NSAttributedString(markdownString: string, options: stylingOptions) { markdownType, string in
switch markdownType {
diff --git a/ios/MullvadVPN/UI appearance/UIMetrics.swift b/ios/MullvadVPN/UI appearance/UIMetrics.swift
index 56f07102b3..7ae20ade94 100644
--- a/ios/MullvadVPN/UI appearance/UIMetrics.swift
+++ b/ios/MullvadVPN/UI appearance/UIMetrics.swift
@@ -129,4 +129,7 @@ extension UIMetrics {
static let padding32: CGFloat = 32
static let padding40: CGFloat = 40
static let padding48: CGFloat = 48
+
+ /// Preferred content size for controllers presented using formsheet modal presentation style.
+ static let preferredFormSheetContentSize = CGSize(width: 480, height: 640)
}
diff --git a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift
index b2436e3c9c..5dbbd3ca4c 100644
--- a/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift
+++ b/ios/MullvadVPN/View controllers/AccountDeletion/AccountDeletionContentView.swift
@@ -314,10 +314,9 @@ class AccountDeletionContentView: UIView {
)
messageLabel.attributedText = NSAttributedString(
markdownString: text,
- options: NSAttributedString
- .MarkdownStylingOptions(
- font: .preferredFont(forTextStyle: .body)
- )
+ options: MarkdownStylingOptions(
+ font: .preferredFont(forTextStyle: .body)
+ )
)
}
}
diff --git a/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogContentView.swift b/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogContentView.swift
deleted file mode 100644
index 39078e1067..0000000000
--- a/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogContentView.swift
+++ /dev/null
@@ -1,144 +0,0 @@
-//
-// ChangeLogContentView.swift
-// MullvadVPN
-//
-// Created by pronebird on 24/03/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import UIKit
-
-final class ChangeLogContentView: UIView {
- private let titleLabel: UILabel = {
- let titleLabel = UILabel()
- titleLabel.font = .systemFont(ofSize: 24, weight: .bold)
- titleLabel.numberOfLines = 0
- titleLabel.textColor = .white
- titleLabel.allowsDefaultTighteningForTruncation = true
- titleLabel.lineBreakMode = .byWordWrapping
- if #available(iOS 14.0, *) {
- // See: https://stackoverflow.com/q/46200027/351305
- titleLabel.lineBreakStrategy = []
- }
- return titleLabel
- }()
-
- private let subheadLabel: UILabel = {
- let subheadLabel = UILabel()
- subheadLabel.font = .systemFont(ofSize: 18, weight: .bold)
- subheadLabel.numberOfLines = 0
- subheadLabel.textColor = .white
- subheadLabel.allowsDefaultTighteningForTruncation = true
- subheadLabel.lineBreakMode = .byWordWrapping
- if #available(iOS 14.0, *) {
- // See: https://stackoverflow.com/q/46200027/351305
- subheadLabel.lineBreakStrategy = []
- }
- subheadLabel.text = NSLocalizedString(
- "CHANGES_IN_THIS_VERSION",
- tableName: "ChangeLog",
- value: "Changes in this version:",
- comment: ""
- )
- return subheadLabel
- }()
-
- private let textView: UITextView = {
- let textView = UITextView()
- textView.backgroundColor = .clear
- textView.isEditable = false
- textView.isSelectable = false
- textView.textContainerInset = UIMetrics.contentInsets
- return textView
- }()
-
- private let okButton: AppButton = {
- let button = AppButton(style: .default)
- button.accessibilityIdentifier = "OkButton"
- button.setTitle(NSLocalizedString(
- "OK_BUTTON",
- tableName: "ChangeLog",
- value: "Got it",
- comment: ""
- ), for: .normal)
- return button
- }()
-
- private let footerContainer: UIView = {
- let container = UIView()
- container.directionalLayoutMargins = UIMetrics.contentLayoutMargins
- container.backgroundColor = .secondaryColor
- return container
- }()
-
- var didTapButton: (() -> Void)?
-
- override init(frame: CGRect) {
- super.init(frame: frame)
-
- backgroundColor = .primaryColor
- directionalLayoutMargins = UIMetrics.contentLayoutMargins
-
- okButton.addTarget(self, action: #selector(handleButtonTap), for: .touchUpInside)
-
- addSubviews()
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
-
- func setApplicationVersion(_ string: String) {
- titleLabel.text = string
- }
-
- func setChangeLogText(_ string: String) {
- let bullet = "• "
- let font = UIFont.systemFont(ofSize: 18)
-
- let bulletList = string.split(whereSeparator: { $0.isNewline })
- .map { "\(bullet)\($0)" }
- .joined(separator: "\n")
-
- let paragraphStyle = NSMutableParagraphStyle()
- paragraphStyle.lineHeightMultiple = 1.5
- paragraphStyle.lineBreakMode = .byWordWrapping
- paragraphStyle.headIndent = bullet.size(withAttributes: [.font: font]).width
-
- textView.attributedText = NSAttributedString(
- string: bulletList,
- attributes: [
- .paragraphStyle: paragraphStyle,
- .font: font,
- .foregroundColor: UIColor.white,
- ]
- )
- }
-
- private func addSubviews() {
- footerContainer.setContentCompressionResistancePriority(.defaultHigh, for: .vertical)
-
- footerContainer.addConstrainedSubviews([okButton]) {
- okButton.pinEdgesToSuperviewMargins()
- }
-
- addConstrainedSubviews([titleLabel, subheadLabel, textView, footerContainer]) {
- titleLabel.pinEdgesToSuperviewMargins(.all().excluding(.bottom))
- subheadLabel.pinEdgesToSuperviewMargins(.init([.leading(0), .trailing(0)]))
- subheadLabel.topAnchor.constraint(
- equalToSystemSpacingBelow: titleLabel.bottomAnchor,
- multiplier: 1
- )
-
- textView.topAnchor.constraint(equalTo: subheadLabel.bottomAnchor)
- textView.pinEdgesToSuperview(.init([.leading(0), .trailing(0)]))
-
- footerContainer.pinEdgesToSuperview(.all().excluding(.top))
- footerContainer.topAnchor.constraint(equalTo: textView.bottomAnchor)
- }
- }
-
- @objc private func handleButtonTap() {
- didTapButton?()
- }
-}
diff --git a/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogViewController.swift b/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogViewController.swift
deleted file mode 100644
index 6bea0b3f37..0000000000
--- a/ios/MullvadVPN/View controllers/ChangeLog/ChangeLogViewController.swift
+++ /dev/null
@@ -1,53 +0,0 @@
-//
-// ChangeLogViewController.swift
-// MullvadVPN
-//
-// Created by pronebird on 24/03/2023.
-// Copyright © 2023 Mullvad VPN AB. All rights reserved.
-//
-
-import UIKit
-
-class ChangeLogViewController: UIViewController, RootContainment {
- // MARK: - RootContainment
-
- var preferredHeaderBarPresentation: HeaderBarPresentation {
- HeaderBarPresentation(style: .default, showsDivider: false)
- }
-
- var prefersHeaderBarHidden: Bool {
- false
- }
-
- var prefersNotificationBarHidden: Bool {
- true
- }
-
- // MARK: - Public
-
- var onFinish: (() -> Void)?
-
- func setApplicationVersion(_ string: String) {
- contentView.setApplicationVersion(string)
- }
-
- func setChangeLogText(_ string: String) {
- contentView.setChangeLogText(string)
- }
-
- // MARK: - View lifecycle
-
- private let contentView = ChangeLogContentView()
-
- override var preferredStatusBarStyle: UIStatusBarStyle {
- .lightContent
- }
-
- override func loadView() {
- view = contentView
-
- contentView.didTapButton = { [weak self] in
- self?.onFinish?()
- }
- }
-}
diff --git a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift
index b55cfaf386..5f4113660d 100644
--- a/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift
+++ b/ios/MullvadVPN/View controllers/OutOfTime/OutOfTimeContentView.swift
@@ -146,7 +146,7 @@ class OutOfTimeContentView: UIView {
func setBodyLabelText(_ text: String) {
bodyLabel.attributedText = NSAttributedString(
markdownString: text,
- options: NSAttributedString.MarkdownStylingOptions(font: .systemFont(ofSize: 17))
+ options: MarkdownStylingOptions(font: .systemFont(ofSize: 17))
)
}
}
diff --git a/ios/MullvadVPN/View controllers/Preferences/PreferencesViewModel.swift b/ios/MullvadVPN/View controllers/Preferences/PreferencesViewModel.swift
index 2c68adb97b..091be496a5 100644
--- a/ios/MullvadVPN/View controllers/Preferences/PreferencesViewModel.swift
+++ b/ios/MullvadVPN/View controllers/Preferences/PreferencesViewModel.swift
@@ -55,7 +55,7 @@ enum CustomDNSPrecondition {
value: "Tap **Edit** to add at least one DNS server.",
comment: "Foot note displayed if there are no DNS entries, but table view is not in editing mode."
),
- options: NSAttributedString.MarkdownStylingOptions(font: preferredFont)
+ options: MarkdownStylingOptions(font: preferredFont)
)
}