Merge branch 'main' into tech/ET-6087

# Conflicts:
#	Modules/App/Tests/Tests/Snapshots/Screens/Sidebar/SidebarScreenSnapshotTests.swift
This commit is contained in:
Maciej Gomółka
2026-05-06 08:56:43 +02:00
120 changed files with 731 additions and 383 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
{
"project": "apple-mail-new",
"locale": "0d39f2e91da281e1a30dfc158cc6c4257d0a7d5a"
"locale": "c3c613cfc201210c3d95bdda446c74a329ffc290"
}
@@ -61,7 +61,7 @@ final class SenderImageAPIDataSource: Sendable, SenderImageDataSource {
address: params.address,
bimiSelector: params.bimiSelector,
displaySenderImage: params.displaySenderImage,
size: 128,
size: .s128,
mode: colorScheme == .dark ? "dark" : "light",
format: "png"
)
@@ -46,7 +46,7 @@ extension SystemLabel {
L10n.Mailbox.SystemFolder.scheduled
case .snoozed:
L10n.Mailbox.SystemFolder.snoozed
case .categorySocial, .categoryPromotions, .catergoryUpdates, .categoryForums, .categoryDefault, .blocked, .pinned:
case .categorySocial, .categoryPromotions, .categoryUpdates, .categoryForums, .categoryDefault, .blocked, .pinned, .categoryNewsletter, .categoryTransactions:
fatalError("Not implemented")
}
}
@@ -75,7 +75,7 @@ extension SystemLabel {
DS.Icon.icClockPaperPlane.image
case .snoozed:
DS.Icon.icClock.image
case .categorySocial, .categoryPromotions, .catergoryUpdates, .categoryForums, .categoryDefault, .blocked, .pinned:
case .categorySocial, .categoryPromotions, .categoryUpdates, .categoryForums, .categoryDefault, .blocked, .pinned, .categoryNewsletter, .categoryTransactions:
fatalError("Not implemented")
}
}
@@ -32,8 +32,4 @@ extension UpsellConfiguration {
false
#endif
}
var humanReadableUpsoldPlanName: String {
"Mail Plus"
}
}
@@ -57066,7 +57066,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktivno vas ščitimo pred sledenjem. Tukaj si lahko ogledate vohunske slikovne pike, ki smo jih blokirali, in povezave za sledenje, ki smo jih očistili v tem sporočilu. [Več o tem](https://proton.me/support/email-tracker-protection)"
"value" : "Aktivno vas ščitimo pred sledenjem. Tukaj si lahko ogledate vohunske slikovne pike, ki smo jih blokirali, in sledilne povezave, ki smo jih očistili v tem sporočilu. [Več o tem](https://proton.me/support/email-tracker-protection)"
}
},
"tr" : {
@@ -397,6 +397,8 @@ extension MailboxModel {
showScrollerErrorIfNotNetwork(error: error)
let isLastPage = await !conversationScrollerHasMore()
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
case .categoryViewChanged:
break
}
}
@@ -443,6 +445,8 @@ extension MailboxModel {
showScrollerErrorIfNotNetwork(error: error)
let isLastPage = await !messageScrollerHasMore()
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
case .categoryViewChanged:
break
}
}
@@ -160,7 +160,7 @@ extension DraftPresenter {
undoScheduleSendProvider: UndoScheduleSendProvider = .mockInstance
) -> DraftPresenter {
.init(
userSession: .init(noHandle: .init()),
userSession: .init(noPointer: .init()),
draftProvider: .dummy,
undoSendProvider: undoSendProvider,
undoScheduleSendProvider: undoScheduleSendProvider
@@ -28,6 +28,6 @@ extension DraftProvider {
}
static var dummy: Self {
.init(makeDraft: { _, _ in NewDraftResult.ok(.init(noHandle: .init())) })
.init(makeDraft: { _, _ in NewDraftResult.ok(.init(noPointer: .init())) })
}
}
@@ -48,6 +48,6 @@ extension LabelAsActions {
extension Undo {
static var dummy: Undo {
Undo(noHandle: .init())
Undo(noPointer: .init())
}
}
@@ -22,7 +22,7 @@ enum LabelAsSheetPreviewProvider {
static func testData() -> LabelAsSheetModel {
.init(
input: .init(sheetType: .labelAs, ids: [], mailboxItem: .message(isLastMessageInCurrentLocation: false)),
mailbox: .init(noHandle: .init()),
mailbox: .init(noPointer: .init()),
availableLabelAsActions: .init(
message: { _, _ in .ok(testLabels()) },
conversation: { _, _ in .ok([]) }
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension MailUserSession {
static var dummy: MailUserSession {
.init(noHandle: .init())
.init(noPointer: .init())
}
}
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension Mailbox {
static var dummy: Mailbox {
.init(noHandle: .init())
.init(noPointer: .init())
}
}
@@ -126,8 +126,8 @@ struct AppProtectionSelectionScreen: View {
.init(type: .faceID, isSelected: true),
]
),
appSettingsRepository: MailSession(noHandle: .init()),
appProtectionConfigurator: MailSession(noHandle: .init())
appSettingsRepository: MailSession(noPointer: .init()),
appProtectionConfigurator: MailSession(noPointer: .init())
)
}
}
@@ -199,7 +199,7 @@ struct AppSettingsScreen: View {
NavigationStack {
AppSettingsScreen(
state: .initial(isDiscreetAppIconEnabled: false),
customSettings: CustomSettings(noHandle: .init())
customSettings: CustomSettings(noPointer: .init())
)
}
}
@@ -185,8 +185,8 @@ private extension SystemLabel {
func emptyScreenVariant(isUnreadFilterOn: Bool) -> NoResultsView.Variant {
switch self {
case .inbox, .allDrafts, .allSent, .sent, .trash, .spam, .allMail, .archive, .drafts, .starred, .scheduled,
.almostAllMail, .snoozed, .categorySocial, .categoryPromotions, .catergoryUpdates, .categoryForums,
.categoryDefault, .blocked, .pinned:
.almostAllMail, .snoozed, .categorySocial, .categoryPromotions, .categoryUpdates, .categoryForums,
.categoryDefault, .blocked, .pinned, .categoryNewsletter, .categoryTransactions:
.mailbox(isUnreadFilterOn: isUnreadFilterOn)
case .outbox:
.outbox
@@ -197,5 +197,5 @@ struct ReportProblemScreen: View {
}
#Preview {
ReportProblemScreen(reportProblemService: MailUserSession(noHandle: .init()))
ReportProblemScreen(reportProblemService: MailUserSession(noPointer: .init()))
}
@@ -231,6 +231,8 @@ final class SearchModel: ObservableObject {
AppLogger.log(error: error, category: .mailbox)
let isLastPage = await !searchScrollerHasMore()
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
case .categoryViewChanged:
break
}
}
@@ -324,7 +324,7 @@ private extension SettingsPreference {
#Preview {
NavigationStack {
SettingsScreen(
mailUserSession: MailUserSession(noHandle: .init()),
mailUserSession: MailUserSession(noPointer: .init()),
accountAuthCoordinator: .mock(),
upsellCoordinator: .init(
mailUserSession: .dummy,
@@ -232,8 +232,6 @@ struct SidebarScreen: View {
@ViewBuilder
private func upsellSidebarItem(item: SidebarItem, upsellType: UpsellType) -> some View {
let planName = UpsellConfiguration.mail.humanReadableUpsoldPlanName
SidebarItemButton(
item: item,
isTappable: isButtonTappable,
@@ -241,7 +239,7 @@ struct SidebarScreen: View {
content: {
HStack(spacing: .zero) {
sidebarItemImage(icon: upsellType.icon.image, isSelected: false, renderingMode: .original)
itemNameLabel(name: upsellType.title(planName: planName), isSelected: false, foregroundColor: upsellType.tint)
itemNameLabel(name: upsellType.title, isSelected: false)
Spacer()
}
}
@@ -370,11 +368,11 @@ struct SidebarScreen: View {
.accessibilityIdentifier(SidebarScreenIdentifiers.badgeIcon)
}
private func itemNameLabel(name: String, isSelected: Bool, foregroundColor: Color? = nil) -> some View {
private func itemNameLabel(name: String, isSelected: Bool) -> some View {
Text(name)
.font(.subheadline)
.fontWeight(isSelected ? .bold : .regular)
.foregroundStyle(foregroundColor ?? (isSelected ? DS.Color.Sidebar.textSelected : DS.Color.Sidebar.textNorm))
.foregroundStyle(isSelected ? DS.Color.Sidebar.textSelected : DS.Color.Sidebar.textNorm)
.lineLimit(1)
.accessibilityIdentifier(SidebarScreenIdentifiers.textItem)
}
@@ -455,24 +453,25 @@ private extension SidebarOtherItem {
}
private extension UpsellType {
var planName: String {
switch self {
case .mailPlus:
"Mail Plus"
case .unlimited:
"Unlimited"
}
}
var icon: ImageResource {
switch self {
case .mailPlus, .unlimited:
case .mailPlus:
DS.Icon.icDiamond
case .unlimited:
DS.Icon.icInfinity
}
}
func title(planName: String) -> String {
switch self {
case .mailPlus, .unlimited:
L10n.Sidebar.upgrade(to: planName).string
}
}
var tint: Color? {
switch self {
case .mailPlus, .unlimited:
nil
}
var title: String {
L10n.Sidebar.upgrade(to: planName).string
}
}
@@ -90,6 +90,8 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
if case .eligible(let upsellType) = upsellEligibility {
ToolbarItem(placement: .topBarTrailing) {
upsellButton(for: upsellType)
.frame(width: 26, height: 26)
.clipShape(.circle)
}
}
ToolbarItem(placement: .topBarTrailing) {
@@ -111,10 +113,10 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
if !selectionMode.hasItems {
ToolbarItemGroup(placement: .topBarTrailing) {
HStack(spacing: DS.Spacing.standard) {
searchButton
if case .eligible(let upsellType) = upsellEligibility {
upsellButton(for: upsellType)
}
searchButton
avatarView()
}
}
@@ -141,15 +143,35 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
}
}
@ViewBuilder
private func upsellButton(for upsellType: UpsellType) -> some View {
Button(L10n.MainToolbar.upgrade, image: upsellType.icon) {
Task {
do {
let upsellScreenModel = try await upsellCoordinator.presentUpsellScreen(entryPoint: .mailboxTopBar, upsellType: upsellType)
onEvent(.onUpsell(upsellScreenModel))
} catch {
toastStateStore.present(toast: .error(message: error.localizedDescription))
}
switch upsellType {
case .mailPlus:
Button(L10n.MainToolbar.upgrade, image: upsellType.icon) {
performUpsellAction(upsellType: upsellType)
}
case .unlimited:
Button {
performUpsellAction(upsellType: upsellType)
} label: {
Image(upsellType.icon)
.renderingMode(.original)
.resizable()
.scaledToFit()
.frame(width: 32, height: 32)
.clipShape(RoundedRectangle(cornerRadius: Theme.radius.large))
}
.accessibilityLabel(L10n.MainToolbar.upgrade)
}
}
private func performUpsellAction(upsellType: UpsellType) {
Task {
do {
let upsellScreenModel = try await upsellCoordinator.presentUpsellScreen(entryPoint: .mailboxTopBar, upsellType: upsellType)
onEvent(.onUpsell(upsellScreenModel))
} catch {
toastStateStore.present(toast: .error(message: error.localizedDescription))
}
}
}
@@ -203,8 +225,10 @@ enum MainToolbarEvent {
private extension UpsellType {
var icon: ImageResource {
switch self {
case .mailPlus, .unlimited:
case .mailPlus:
DS.Icon.icBrandProtonMailUpsellBlackAndWhite
case .unlimited:
DS.Icon.icBrandProtonUnlimitedUpsellHeader
}
}
}
+3 -3
View File
@@ -26,11 +26,11 @@ final class MailboxStub: Mailbox, @unchecked Sendable {
init(viewMode: ViewMode) {
self._viewMode = viewMode
super.init(noHandle: .init())
super.init(noPointer: .init())
}
@available(*, unavailable)
required init(unsafeFromHandle handle: UInt64) {
fatalError("init(unsafeFromHandle:) has not been implemented")
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
}
+1 -1
View File
@@ -67,6 +67,6 @@ final class SidebarSpy: SidebarProtocol, @unchecked Sendable {
let mappedIndex: CallbackIndex = lastIndex == -1 ? .folder : .init(rawValue: lastIndex + 1)!
spiedWatchers[mappedIndex] = callback
return .ok(WatchHandleDummy(noHandle: .init()))
return .ok(WatchHandleDummy(noPointer: .init()))
}
}
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension MailUserSession {
static var dummy: MailUserSession {
.init(noHandle: .init())
.init(noPointer: .init())
}
}
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension Mailbox {
static var dummy: Mailbox {
.init(noHandle: .init())
.init(noPointer: .init())
}
}
@@ -40,7 +40,7 @@ final class MailboxItemCellSnapshotTests {
("expiration_time_message", .makeSimpleMessage(type: .expirationTime)),
])
func mailboxItemCell(testName: String, model: MailboxItemCellUIModel) {
assertSnapshotsOnIPhoneX(of: MailboxItemCell.testCell(model: model), testName: testName)
assertSnapshotsOnIPhoneX(of: MailboxItemCell.testCell(model: model), precision: 0.99, testName: testName)
}
}
@@ -27,7 +27,7 @@ import XCTest
@MainActor
class SettingsScreenSnapshotTests: BaseTestCase {
func testSettingsScreenLayoutsCorrectOnIphoneX() {
let store = AppAppearanceStore(mailSession: { MailSession(noHandle: .init()) })
let store = AppAppearanceStore(mailSession: { MailSession(noPointer: .init()) })
let mailUserSession = MailUserSessionSpy(id: "")
mailUserSession.stubbedAccountDetails = .testData
mailUserSession.stubbedUser = .testData
@@ -37,9 +37,10 @@ final class SidebarScreenSnapshotTests {
createFolder: .createFolder
)
@Test(arguments: [UIUserInterfaceStyle.light, .dark])
func testSidebarWithDataLayoutsCorrectOnIphoneX(style: UIUserInterfaceStyle) {
@Test(arguments: [UIUserInterfaceStyle.light, .dark], [UpsellType.mailPlus, .unlimited])
func testSidebarWithDataLayoutsCorrectOnIphoneX(style: UIUserInterfaceStyle, upsellType: UpsellType) {
var state = self.state
state.upsell = .upsell(upsellType)
state.folders = [SidebarCustomFolder.topSecretFolder].map(\.sidebarFolder)
state.system = [PMSystemLabel.inbox, .sent, .outbox].compactMap(\.sidebarSystemFolder)
@@ -48,7 +49,7 @@ final class SidebarScreenSnapshotTests {
let screenModel = SidebarModel(
state: state,
sidebar: SidebarSpy(),
upsellEligibilityPublisher: .init(constant: .eligible(.mailPlus))
upsellEligibilityPublisher: .init(constant: .eligible(upsellType))
)
let sidebarScreen = SidebarScreen(
screenModel: screenModel,
@@ -57,7 +58,7 @@ final class SidebarScreenSnapshotTests {
)
.environmentObject(AppUIStateStore(sidebarState: .init(zIndex: .zero, visibleWidth: 320)))
assertSnapshotsOnIPhoneX(of: sidebarScreen, styles: [style])
assertSnapshotsOnIPhoneX(of: sidebarScreen, named: "\(upsellType)", styles: [style])
}
@Test(arguments: [UIUserInterfaceStyle.light, .dark])
@@ -25,12 +25,24 @@ import proton_app_uniffi
@testable import InboxIAP
@MainActor
@Suite(.disabled("Tests are failing on CI only, needs more investigation."))
struct UpsellScreenSnapshotTests {
enum SelectedCycle: String {
case yearly
case monthly
var lengthInMonths: Int {
switch self {
case .yearly: 12
case .monthly: 1
}
}
}
struct TestCase {
let label: String
let config: ViewImageConfig
let upsellType: UpsellType
let selectedCycle: SelectedCycle
}
nonisolated private static let testCases: [TestCase] = {
@@ -42,15 +54,19 @@ struct UpsellScreenSnapshotTests {
]
let upsellTypes: [UpsellType] = [.mailPlus, .unlimited]
let selectedCycles: [SelectedCycle] = [.yearly, .monthly]
return orientations.flatMap { orientation in
devices.flatMap { device in
upsellTypes.map { upsellType in
.init(
label: "\(device.label)_\(orientation)_\(upsellType.label)",
config: device.configFactory(orientation),
upsellType: upsellType
)
upsellTypes.flatMap { upsellType in
selectedCycles.map { selectedCycle in
.init(
label: "\(device.label)_\(orientation)_\(upsellType.label)_\(selectedCycle.rawValue)",
config: device.configFactory(orientation),
upsellType: upsellType,
selectedCycle: selectedCycle
)
}
}
}
}
@@ -58,12 +74,20 @@ struct UpsellScreenSnapshotTests {
@Test(arguments: testCases)
func upsellScreen(testCase: TestCase) {
let sut = UpsellScreen(model: .preview(entryPoint: .mailboxTopBar, upsellType: testCase.upsellType))
let model = UpsellScreenModel.preview(entryPoint: .mailboxTopBar, upsellType: testCase.upsellType)
if let instance = model.planInstances.first(where: { $0.cycleInMonths == testCase.selectedCycle.lengthInMonths }) {
model.selectedInstanceId = instance.storeKitProductId
}
let sut = UpsellScreen(model: model)
let viewController = UIHostingController(rootView: sut)
viewController.view.backgroundColor = .black
viewController.overrideUserInterfaceStyle = .dark
let strategy: Snapshotting<UIViewController, UIImage> = .image(
on: testCase.config,
drawHierarchyInKeyWindow: true
drawHierarchyInKeyWindow: true,
precision: 0.99
)
assertSnapshot(of: viewController, as: strategy, named: testCase.label)
@@ -0,0 +1,69 @@
// Copyright (c) 2025 Proton Technologies AG
//
// This file is part of Proton Mail.
//
// Proton Mail is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
import InboxDesignSystem
import InboxIAP
import InboxSnapshotTesting
import InboxTesting
import ProtonUIFoundations
import SwiftUI
import Testing
import UIKit
import proton_app_uniffi
@testable import ProtonMail
@MainActor
final class MainToolbarSnapshotTests {
@Test(arguments: [UpsellType.mailPlus, .unlimited])
func mainToolbarWithUpsell(upsellType: UpsellType) {
let view = makeToolbarView(upsellEligibility: .eligible(upsellType))
assertSnapshotsOnIPhoneX(
of: view,
named: "upsellType.\(upsellType)",
drawHierarchyInKeyWindow: true,
precision: 0.99
)
}
@Test
func mainToolbarWithoutUpsell() {
let view = makeToolbarView(upsellEligibility: .notEligible)
assertSnapshotsOnIPhoneX(of: view, drawHierarchyInKeyWindow: true)
}
private func makeToolbarView(upsellEligibility: UpsellEligibility) -> some View {
NavigationStack {
Color.clear
.mainToolbar(
title: "Inbox",
onEvent: { _ in },
avatarView: {
Text("R")
.font(.subheadline.weight(.semibold))
.frame(width: 32, height: 32)
.foregroundStyle(.white)
.background(.blue)
.clipShape(RoundedRectangle(cornerRadius: Theme.radius.large))
}
)
}
.environment(\.upsellEligibility, upsellEligibility)
.environmentObject(ToastStateStore(initialState: .initial))
.environmentObject(UpsellCoordinator.dummy)
}
}
@@ -35,7 +35,7 @@ final class SceneDelegateTests: BaseTestCase {
sut = .init()
mailSessionSpy = .init()
sut.appProtectionStore = .init(mailSession: { self.mailSessionSpy })
sut.mailSessionFactory = { .init(noHandle: .init()) }
sut.mailSessionFactory = { .init(noPointer: .init()) }
sut.checkAutoLockSetting = { completion in completion(self.shouldAutoLockStub) }
sut.transitionAnimation = { _, _, _, animation, completion in
animation?()
@@ -33,7 +33,7 @@ final class DeviceTokenRegistrarTests {
var lastCreatedHandle: RegisterDeviceTaskHandleSpy?
mailSession.stubbedRegisterDeviceTaskHandleFactory = {
let newHandle = RegisterDeviceTaskHandleSpy(noHandle: .init())
let newHandle = RegisterDeviceTaskHandleSpy(noPointer: .init())
lastCreatedHandle = newHandle
return newHandle
}
@@ -86,7 +86,7 @@ final class DraftPresenterTests: BaseTestCase, @unchecked Sendable {
@MainActor
func testOpenDraftWithContact_ItCreatesEmptyDraftAddRecipientAndOpensDraft() async throws {
let draftSpy = DraftSpy(noHandle: .init())
let draftSpy = DraftSpy(noPointer: .init())
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
var capturedDraftToPresent: [DraftToPresent] = []
@@ -123,7 +123,7 @@ final class DraftPresenterTests: BaseTestCase, @unchecked Sendable {
@MainActor
func testOpenDraftWithContactGroup_ItCreatesEmptyDraftAddGroupAndOpensDraft() async throws {
let draftSpy = DraftSpy(noHandle: .init())
let draftSpy = DraftSpy(noPointer: .init())
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
var capturedDraftToPresent: [DraftToPresent] = []
@@ -292,13 +292,13 @@ extension DraftPresenterTests {
}
private extension Draft {
static var dummyDraft: Draft { .init(noHandle: .init()) }
static var dummyDraft: Draft { .init(noPointer: .init()) }
}
private class DraftSpy: Draft, @unchecked Sendable {
let toRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
let ccRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
let bccRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
let toRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
let ccRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
let bccRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
private(set) var setSubjectCalls: [String] = []
private(set) var setBodyCalls: [String] = []
@@ -31,7 +31,7 @@ final class LabelAsSheetModelTests {
private var invokedAvailableActionsWithConversationIDs: [ID] = []
private var invokedDismissCount = 0
private var stubbedLabelAsActions: [LabelAsAction] = []
private var stubbedLabelAsOutput: LabelAsOutput = .init(inputLabelIsEmpty: false, undo: UndoSpy(noHandle: .init()))
private var stubbedLabelAsOutput: LabelAsOutput = .init(inputLabelIsEmpty: false, undo: UndoSpy(noPointer: .init()))
private var toastStateStore = ToastStateStore(initialState: .initial)
private var invokedLabelMessage: [LabelAsExecutedWithData] = []
@@ -175,7 +175,7 @@ final class LabelAsSheetModelTests {
private func sut(ids: [ID], type: MailboxItemType) -> LabelAsSheetModel {
LabelAsSheetModel(
input: .init(sheetType: .labelAs, ids: ids, mailboxItem: type.mailboxItem),
mailbox: .init(noHandle: .init()),
mailbox: .init(noPointer: .init()),
availableLabelAsActions: .init(
message: { _, ids in
self.invokedAvailableActionsWithMessagesIDs = ids
@@ -220,7 +220,7 @@ final class LabelAsSheetModelTests {
itemType: MailboxItemType,
spyToVerify: () -> [LabelAsExecutedWithData]
) async throws {
let undoSpy = UndoSpy(noHandle: .init())
let undoSpy = UndoSpy(noPointer: .init())
let selectedLabelID: ID = .init(value: 2)
let partiallySelectedLabelID: ID = .init(value: 4)
stubbedLabelAsActions = [
@@ -257,7 +257,7 @@ final class LabelAsSheetModelTests {
spyToVerify: () -> [LabelAsExecutedWithData],
expectToastMessage: LocalizedStringResource
) async throws {
let undoSpy = UndoSpy(noHandle: .init())
let undoSpy = UndoSpy(noPointer: .init())
let selectedLabelID: ID = .init(value: 2)
let partiallySelectedLabelID: ID = .init(value: 4)
stubbedLabelAsActions = [
@@ -259,7 +259,7 @@ class ListActionsToolbarStoreTests {
func action_WhenMoveToInboxIsTappedUndoIsAvailbleAndTapped_ItTriggersUndoAndDismissesToast() async throws {
let ids: [ID] = [.init(value: 7), .init(value: 77)]
let systemFolder = MovableSystemFolderAction.testInbox
let undoSpy = UndoSpy(noHandle: .init())
let undoSpy = UndoSpy(noPointer: .init())
let viewMode = ViewMode.messages
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
sut = makeSUT(viewMode: viewMode)
@@ -29,7 +29,7 @@ final class MoveToActionPerformerTests: BaseTestCase {
super.setUp()
sut = .init(
mailbox: .init(noHandle: .init()),
mailbox: .init(noPointer: .init()),
moveToActions: .init(
moveMessagesTo: { [unowned self] _, _, _ in stubbedResult },
moveConversationsTo: { [unowned self] _, _, _ in stubbedResult }
@@ -108,7 +108,7 @@ final class MoveToSheetStateStoreTests {
@Test
func action_WhenInboxIsTappedUndoIsAvailableAndTapped_ItTriggersUndoAndDismissesToast() async throws {
let undoSpy = UndoSpy(noHandle: .init())
let undoSpy = UndoSpy(noPointer: .init())
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
let sut = sut(
@@ -155,7 +155,7 @@ final class MoveToSheetStateStoreTests {
.init(
state: .initial,
input: input,
mailbox: .init(noHandle: .init()),
mailbox: .init(noPointer: .init()),
availableMoveToActions: .init(
message: { _, ids in
self.invokedAvailableActionsWithMessagesIDs = ids
@@ -566,12 +566,12 @@ private final class DecryptedMessageSpy: DecryptedMessage, @unchecked Sendable {
init(stubbedOptions: TransformOpts) {
self.stubbedOptions = stubbedOptions
super.init(noHandle: .init())
super.init(noPointer: .init())
}
@available(*, unavailable)
required init(unsafeFromHandle handle: UInt64) {
fatalError("init(unsafeFromHandle:) has not been implemented")
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
private(set) var bodyWithOptionsCalls: [TransformOpts] = []
@@ -333,7 +333,7 @@ final class MessageAddressActionViewStateStoreTests {
senderUnblocker: .init(
mailbox: .dummy,
wrapper: .init(
messageBody: { _, _ in .ok(.init(noHandle: .init())) },
messageBody: { _, _ in .ok(.init(noPointer: .init())) },
markMessageHam: { _, _ in .ok },
unblockSender: { _, emailAddress in
await self.unblockSpy.result(for: emailAddress)
@@ -235,12 +235,12 @@ class BackgroundExecutionHandleStub: BackgroundExecutionHandle, @unchecked Senda
private(set) var abortCalls: [Bool] = []
init() {
super.init(noHandle: .init())
super.init(noPointer: .init())
}
@available(*, unavailable)
required init(unsafeFromHandle handle: UInt64) {
fatalError("init(unsafeFromHandle:) has not been implemented")
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
// MARK: - BackgroundExecutionHandle
@@ -127,7 +127,7 @@ struct ComposerLoadingView: View {
ComposerScreen(
draft: .emptyMock,
draftOrigin: .new,
dependencies: .init(contactProvider: .mockInstance, userSession: .init(noHandle: .init())),
dependencies: .init(contactProvider: .mockInstance, userSession: .init(noPointer: .init())),
onDismiss: { _ in }
)
.environmentObject(toastStateStore)
@@ -307,6 +307,6 @@ final class MockAttachmentList: AttachmentListProtocol, @unchecked Sendable {
}
func watcherStream() async -> AttachmentListWatcherStreamResult {
.ok(DraftAttachmentListUpdateStream.init(noHandle: .init()))
.ok(DraftAttachmentListUpdateStream.init(noPointer: .init()))
}
}
@@ -141,7 +141,7 @@ private extension Array where Element == ContactSuggestion {
private extension MailUserSession {
static func empty() -> MailUserSession {
MailUserSession(noHandle: .init())
MailUserSession(noPointer: .init())
}
}
@@ -150,12 +150,12 @@ private class ContactSuggestionsStub: ContactSuggestions, @unchecked Sendable {
init(all: [ContactSuggestion]) {
_all = all
super.init(noHandle: .init())
super.init(noPointer: .init())
}
@available(*, unavailable)
required init(unsafeFromHandle handle: UInt64) {
fatalError("init(unsafeFromHandle:) has not been implemented")
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
override func all() -> [ContactSuggestion] {
@@ -394,6 +394,16 @@ public extension PasswordFlowSubmitTotpResult {
}
}
}
public extension ResolveMessageFromPushNotificationResult {
func get() throws(ActionError) -> Message {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension ResolveMessageIdResult {
func get() throws(ActionError) -> Id {
switch self {
@@ -139,7 +139,7 @@ public struct ContactsScreen: View {
#Preview {
ContactsScreen(
apiConfig: .debugPreview,
mailUserSession: .init(noHandle: .init()),
mailUserSession: .init(noPointer: .init()),
contactsProvider: .previewInstance(),
contactsWatcher: .previewInstance(),
draftPresenter: ContactsDraftPresenterDummy()
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension ContactsWatcher {
static func previewInstance() -> Self {
.init(watch: { _, _ in .ok(.init(contactList: [], handle: .init(noHandle: .init()))) })
.init(watch: { _, _ in .ok(.init(contactList: [], handle: .init(noPointer: .init()))) })
}
}
@@ -19,6 +19,6 @@ import proton_app_uniffi
extension MailUserSession {
static func testInstance() -> MailUserSession {
.init(noHandle: .init())
.init(noPointer: .init())
}
}
@@ -39,7 +39,7 @@ final class ContactSuggestionsRepositoryTests {
let result = ContactSuggestionsStub(all: self.stubbedAllContacts)
return .ok(result)
}),
mailUserSession: MailUserSession(noHandle: .init())
mailUserSession: MailUserSession(noPointer: .init())
)
}
@@ -349,12 +349,12 @@ private class ContactSuggestionsStub: ContactSuggestions, @unchecked Sendable {
init(all: [ContactSuggestion]) {
_all = all
super.init(noHandle: .init())
super.init(noPointer: .init())
}
@available(*, unavailable)
required init(unsafeFromHandle handle: UInt64) {
fatalError("init(unsafeFromHandle:) has not been implemented")
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
fatalError("init(unsafeFromRawPointer:) has not been implemented")
}
override func all() -> [ContactSuggestion] {
@@ -26,7 +26,7 @@ import proton_app_uniffi
final class ContactViewFactoryTests {
let sut = ContactViewFactory(
apiConfig: .testData,
mailUserSession: .init(noHandle: .init()),
mailUserSession: .init(noPointer: .init()),
draftPresenter: ContactsDraftPresenterDummy()
)
@@ -627,7 +627,7 @@ final class ContactsStateStoreTests {
},
contactsWatcher: .init(watch: { [unowned self] _, callback in
watchContactsCallback = callback
return WatchContactListResult.ok(.init(contactList: [], handle: .init(noHandle: .init())))
return WatchContactListResult.ok(.init(contactList: [], handle: .init(noPointer: .init())))
})
),
makeContactsLiveQuery: { [unowned self] in
@@ -23,7 +23,7 @@ import proton_app_uniffi
final class GroupedContactsRepositoryTests {
private lazy var sut: GroupedContactsRepository = .init(
mailUserSession: MailUserSession(noHandle: .init()),
mailUserSession: MailUserSession(noPointer: .init()),
contactsProvider: .init(allContacts: { _ in .ok(self.stubbedContacts) })
)
private var stubbedContacts: [GroupedContacts] = []
@@ -54,6 +54,26 @@ public extension ChallengeLoaderPutResult {
}
}
}
public extension ConversationScrollerCategoryViewResult {
func get() throws(MailScrollerError) -> CategoryView {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension ConversationScrollerChangeCategoryViewResult {
func get() throws(MailScrollerError) {
switch self {
case .ok:
break
case .error(let error):
throw error
}
}
}
public extension ConversationScrollerChangeFilterResult {
func get() throws(MailScrollerError) {
switch self {
@@ -1184,6 +1204,26 @@ public extension MailboxWatchUnreadCountResult {
}
}
}
public extension MessageScrollerCategoryViewResult {
func get() throws(MailScrollerError) -> CategoryView {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension MessageScrollerChangeCategoryViewResult {
func get() throws(MailScrollerError) {
switch self {
case .ok:
break
case .error(let error):
throw error
}
}
}
public extension MessageScrollerChangeFilterResult {
func get() throws(MailScrollerError) {
switch self {
@@ -1344,96 +1384,6 @@ public extension NewMailboxResult {
}
}
}
public extension PasswordFlowChangeMboxPassResult {
func get() throws(PasswordError) -> SimplePasswordState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowChangePassResult {
func get() throws(PasswordError) -> SimplePasswordState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowFidoDetailsResult {
func get() throws(PasswordError) -> Fido2ResponseFfi? {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowHasFidoResult {
func get() throws(PasswordError) -> Bool {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowHasMbpResult {
func get() throws(PasswordError) -> Bool {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowHasTotpResult {
func get() throws(PasswordError) -> Bool {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowStepBackResult {
func get() throws(PasswordError) -> SimplePasswordState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowSubmitFidoResult {
func get() throws(PasswordError) -> SimplePasswordState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension PasswordFlowSubmitTotpResult {
func get() throws(PasswordError) -> SimplePasswordState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension ResolveSystemLabelByIdResult {
func get() throws(ProtonError) -> SystemLabel? {
switch self {
@@ -1564,116 +1514,6 @@ public extension SearchScrollerTotalResult {
}
}
}
public extension SignupFlowAvailableCountriesResult {
func get() throws(SignupError) -> Countries {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowAvailableDomainsResult {
func get() throws(SignupError) -> [String] {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowCompleteResult {
func get() throws(SignupError) -> UserAddrId {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowCreateResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSkipRecoveryResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowStepBackResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSubmitExternalUsernameResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSubmitInternalUsernameResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSubmitPasswordResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSubmitRecoveryEmailResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension SignupFlowSubmitRecoveryPhoneResult {
func get() throws(SignupError) -> SimpleSignupState {
switch self {
case .ok(let value):
value
case .error(let error):
throw error
}
}
}
public extension UpdateNextMessageOnMoveResult {
func get() throws(UserSessionError) {
switch self {
@@ -4852,7 +4852,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "[Preverjanje pošiljatelja](https://proton.me/support/sender-verification-failed) je spodletelo, ker ni bilo podpisano. Prosimo, potrdite pristnost e-pošte pri svojemu stiku."
"value" : "[Preverjanje pošiljatelja](https://proton.me/support/sender-verification-failed) je spodletelo, ker ni bilo podpisano. Potrdite pristnost e-pošte pri svojemu stiku."
}
},
"sv" : {
@@ -5001,7 +5001,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "[Preverjanje pošiljatelja](https://proton.me/support/sender-verification-failed) ni uspelo. Prosimo, potrdite pristnost e-pošte pri svojemu stiku."
"value" : "[Preverjanje pošiljatelja](https://proton.me/support/sender-verification-failed) ni uspelo. Potrdite pristnost e-pošte pri svojemu stiku."
}
},
"sv" : {
@@ -5150,7 +5150,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Sporočilo je na strežnikih Proton shranjeno z [šifriranjem brez dostopa](https://proton.me/blog/zero-access-encryption). Niti Proton niti nihče drug ga ne more prebrati."
"value" : "Sporočilo je na strežnikih Proton shranjeno s [šifriranjem brez dostopa](https://proton.me/blog/zero-access-encryption). Niti Proton niti nihče drug ga ne more prebrati."
}
},
"tr" : {
@@ -5293,7 +5293,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Prejemnik je [preverjen stik](https://proton.me/support/address-verification), kateremu ste zaupali šifrirne ključe."
"value" : "Prejemnik je [preverjen stik](https://proton.me/support/address-verification), njegove šifrirne ključe ste potrdili kot zaupanja vredne."
}
},
"sv" : {
@@ -5442,7 +5442,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Prejemniki so [preverjeni stiki](https://proton.me/support/address-verification), katerim ste zaupali šifrirne ključe."
"value" : "Prejemniki so [preverjeni stiki](https://proton.me/support/address-verification), njihove šifrirne ključe ste potrdili kot zaupanja vredne."
}
},
"sv" : {
@@ -5591,7 +5591,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pošiljatelj je [preverjen stik](https://proton.me/support/address-verification), kateremu ste zaupali šifrirne ključe."
"value" : "Pošiljatelj je [preverjen stik](https://proton.me/support/address-verification), njegove šifrirne ključe ste potrdili kot zaupanja vredne."
}
},
"sv" : {
@@ -5883,7 +5883,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ta e-pošta je na strežnikih Proton shranjena z [šifriranjem brez dostopa](https://proton.me/blog/zero-access-encryption). Niti Proton niti kdorkoli drug je ne more prebrati."
"value" : "Ta e-pošta je na strežnikih Proton shranjena s [šifriranjem brez dostopa](https://proton.me/blog/zero-access-encryption). Niti Proton niti kdorkoli drug je ne more prebrati."
}
},
"tr" : {
@@ -7016,7 +7016,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Šifrirano brez dostopa za preverjenega prejemnika"
"value" : "Šifrirano brez dostopa s preverjenim prejemnikom"
}
},
"sv" : {
@@ -7159,7 +7159,7 @@
"sl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Šifrirano brez dostopa za preverjene prejemnike"
"value" : "Šifrirano brez dostopa s preverjenimi prejemniki"
}
},
"sv" : {
@@ -87,6 +87,7 @@ public extension DS.Icon {
public extension DS.Icon {
static let icBrandProtonMailUpsell = ImageResource.icBrandProtonMailUpsell
static let icBrandProtonMailUpsellBlackAndWhite = ImageResource.icBrandProtonMailUpsellBw
static let icBrandProtonUnlimitedUpsellHeader = ImageResource.icBrandProtonUnlimitedUpsellHeader
static let upsellBlackFridayHeaderButtonWave1 = ImageResource.upsellBlackFridayHeaderButtonWave1
static let upsellBlackFridayHeaderButtonWave2 = ImageResource.upsellBlackFridayHeaderButtonWave2
static let upsellBlackFridaySidebarItemWave1 = ImageResource.upsellBlackFridaySidebarItemWave1
@@ -98,6 +99,9 @@ public extension DS.Icon {
public extension DS.Icon {
static let icCode = ImageResource.icCode
static let icDiamond = ImageResource.icDiamond
static let icInfinity = ImageResource.icInfinity
static let icInfinityUpsellHeader = ImageResource.icInfinityUpsellHeader
static let icInfinityUpsellRow = ImageResource.icInfinityUpsellRow
static let icEnvelopeDot = ImageResource.icEnvelopeDot
static let icEnvelopeOpen = ImageResource.icEnvelopeOpen
static let icFileLines = ImageResource.icFileLines
@@ -41,6 +41,7 @@ public extension DS.Images {
public static let logoMobileSignature = ImageResource.upsellLogoMobileSignature
public static let logoScheduleSend = ImageResource.upsellLogoScheduleSend
public static let logoSnooze = ImageResource.upsellLogoSnooze
public static let logoUnlimited = ImageResource.upsellLogoUnlimited
public enum BlackFriday {
public static let background = ImageResource.upsellBlackFridayBackground
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "Frame 944441499.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Plan icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "Icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "icon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "upsell_logo_unlimited.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -95,7 +95,7 @@ public final class UpsellCoordinator: ObservableObject {
let availablePlans = try await fetchAvailablePlans()
let model = try upsellScreenFactory.upsellScreenModel(
showingPlan: configuration.regularPlan,
showingPlan: upsellType.planVariant,
basedOn: availablePlans,
entryPoint: entryPoint,
upsellType: upsellType
+15
View File
@@ -114,6 +114,12 @@ enum L10n {
return .init("\(measurement.formatted()) storage", bundle: .module, comment: "Amount of storage space available in a given plan, for example: 1 GB storage")
}
static let hideMyEmailAliases = LocalizedStringResource("Hide My Email aliases", bundle: .module, comment: "Description of a feature of a paid subscription")
static let foldersAndLabels = LocalizedStringResource("Folders and labels", bundle: .module, comment: "Description of a feature of a paid subscription")
static let premiumVpnPasswordManagerCloudStorage = LocalizedStringResource(
"Premium VPN, password manager and cloud storage", bundle: .module, comment: "Description of a feature of a paid subscription")
static let darkWebMonitoring = LocalizedStringResource("Dark Web Monitoring", bundle: .module, comment: "Description of a feature of a paid subscription")
static func numberOfEmailAddresses(_ amount: UInt) -> LocalizedStringResource {
.init("\(amount) email addresses", bundle: .module, comment: "Number of email addresses available in a given plan")
}
@@ -130,6 +136,15 @@ enum L10n {
comment: "Notice at the bottom of the upsell page"
)
}
static func autoRenewalNotice(price: String, period: Product.SubscriptionPeriod.Unit) -> LocalizedStringResource {
let periodSuffix = "/\(period.localizedDescription.lowercased())"
return .init(
"Auto-renews at \(price)\(periodSuffix) unless canceled",
bundle: .module,
comment: "Disclaimer shown at the bottom of the upsell page; first placeholder is the localized price, second is the period suffix (/year or /month)"
)
}
}
private extension LocalizedStringResource.BundleDescription {
@@ -758,6 +758,9 @@
}
}
},
"Auto-renews at %@%@ unless canceled" : {
"comment" : "Disclaimer shown at the bottom of the upsell page; first placeholder is the localized price, second is the period suffix (/year or /month)"
},
"Auto-renews at the same price and terms unless canceled" : {
"comment" : "Notice at the bottom",
"localizations" : {
@@ -2626,6 +2629,9 @@
}
}
},
"Dark Web Monitoring" : {
"comment" : "Description of a feature of a paid subscription"
},
"Deletes spam and trash after 30 days. Get this and more with %@." : {
"comment" : "Subtitle of the upsell page",
"localizations" : {
@@ -3169,6 +3175,9 @@
}
}
},
"Folders and labels" : {
"comment" : "Description of a feature of a paid subscription"
},
"Free" : {
"comment" : "Name of the free plan",
"localizations" : {
@@ -3742,6 +3751,9 @@
}
}
},
"Hide My Email aliases" : {
"comment" : "Description of a feature of a paid subscription"
},
"Make your mobile signature your own. Enjoy this and more with %@." : {
"comment" : "Subtitle of the upsell page",
"localizations" : {
@@ -5037,6 +5049,9 @@
}
}
},
"Premium VPN, password manager and cloud storage" : {
"comment" : "Description of a feature of a paid subscription"
},
"Priority customer support" : {
"comment" : "Description of a feature of a paid subscription",
"localizations" : {
@@ -64,7 +64,12 @@ final class TelemetryReporter: TelemetryReporting {
}
private func generalDimensions() -> GeneralDimensions {
.init(upsellEntryPoint: entryPoint!, planBeforeUpgrade: "free", modalVariant: .comparisonPlus)
.init(
upsellEntryPoint: entryPoint!,
planBeforeUpgrade: "free",
modalVariant: .comparisonPlus,
upsellFeatureFlags: upsellFeatureFlagsForIos()
)
}
private func planSpecificDimensions(storeKitProductID: String) -> PlanSpecificDimensions {

Some files were not shown because too many files have changed in this diff Show More