Merge branch 'main' into tech/ET-6087
# Conflicts: # Modules/App/Tests/Tests/Snapshots/Screens/Sidebar/SidebarScreenSnapshotTests.swift
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"project": "apple-mail-new",
|
"project": "apple-mail-new",
|
||||||
"locale": "0d39f2e91da281e1a30dfc158cc6c4257d0a7d5a"
|
"locale": "c3c613cfc201210c3d95bdda446c74a329ffc290"
|
||||||
}
|
}
|
||||||
@@ -61,7 +61,7 @@ final class SenderImageAPIDataSource: Sendable, SenderImageDataSource {
|
|||||||
address: params.address,
|
address: params.address,
|
||||||
bimiSelector: params.bimiSelector,
|
bimiSelector: params.bimiSelector,
|
||||||
displaySenderImage: params.displaySenderImage,
|
displaySenderImage: params.displaySenderImage,
|
||||||
size: 128,
|
size: .s128,
|
||||||
mode: colorScheme == .dark ? "dark" : "light",
|
mode: colorScheme == .dark ? "dark" : "light",
|
||||||
format: "png"
|
format: "png"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ extension SystemLabel {
|
|||||||
L10n.Mailbox.SystemFolder.scheduled
|
L10n.Mailbox.SystemFolder.scheduled
|
||||||
case .snoozed:
|
case .snoozed:
|
||||||
L10n.Mailbox.SystemFolder.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")
|
fatalError("Not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +75,7 @@ extension SystemLabel {
|
|||||||
DS.Icon.icClockPaperPlane.image
|
DS.Icon.icClockPaperPlane.image
|
||||||
case .snoozed:
|
case .snoozed:
|
||||||
DS.Icon.icClock.image
|
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")
|
fatalError("Not implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,4 @@ extension UpsellConfiguration {
|
|||||||
false
|
false
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
var humanReadableUpsoldPlanName: String {
|
|
||||||
"Mail Plus"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57066,7 +57066,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"tr" : {
|
||||||
|
|||||||
@@ -397,6 +397,8 @@ extension MailboxModel {
|
|||||||
showScrollerErrorIfNotNetwork(error: error)
|
showScrollerErrorIfNotNetwork(error: error)
|
||||||
let isLastPage = await !conversationScrollerHasMore()
|
let isLastPage = await !conversationScrollerHasMore()
|
||||||
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
||||||
|
case .categoryViewChanged:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -443,6 +445,8 @@ extension MailboxModel {
|
|||||||
showScrollerErrorIfNotNetwork(error: error)
|
showScrollerErrorIfNotNetwork(error: error)
|
||||||
let isLastPage = await !messageScrollerHasMore()
|
let isLastPage = await !messageScrollerHasMore()
|
||||||
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
||||||
|
case .categoryViewChanged:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ extension DraftPresenter {
|
|||||||
undoScheduleSendProvider: UndoScheduleSendProvider = .mockInstance
|
undoScheduleSendProvider: UndoScheduleSendProvider = .mockInstance
|
||||||
) -> DraftPresenter {
|
) -> DraftPresenter {
|
||||||
.init(
|
.init(
|
||||||
userSession: .init(noHandle: .init()),
|
userSession: .init(noPointer: .init()),
|
||||||
draftProvider: .dummy,
|
draftProvider: .dummy,
|
||||||
undoSendProvider: undoSendProvider,
|
undoSendProvider: undoSendProvider,
|
||||||
undoScheduleSendProvider: undoScheduleSendProvider
|
undoScheduleSendProvider: undoScheduleSendProvider
|
||||||
|
|||||||
@@ -28,6 +28,6 @@ extension DraftProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static var dummy: Self {
|
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 {
|
extension Undo {
|
||||||
static var dummy: Undo {
|
static var dummy: Undo {
|
||||||
Undo(noHandle: .init())
|
Undo(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ enum LabelAsSheetPreviewProvider {
|
|||||||
static func testData() -> LabelAsSheetModel {
|
static func testData() -> LabelAsSheetModel {
|
||||||
.init(
|
.init(
|
||||||
input: .init(sheetType: .labelAs, ids: [], mailboxItem: .message(isLastMessageInCurrentLocation: false)),
|
input: .init(sheetType: .labelAs, ids: [], mailboxItem: .message(isLastMessageInCurrentLocation: false)),
|
||||||
mailbox: .init(noHandle: .init()),
|
mailbox: .init(noPointer: .init()),
|
||||||
availableLabelAsActions: .init(
|
availableLabelAsActions: .init(
|
||||||
message: { _, _ in .ok(testLabels()) },
|
message: { _, _ in .ok(testLabels()) },
|
||||||
conversation: { _, _ in .ok([]) }
|
conversation: { _, _ in .ok([]) }
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
extension MailUserSession {
|
extension MailUserSession {
|
||||||
static var dummy: MailUserSession {
|
static var dummy: MailUserSession {
|
||||||
.init(noHandle: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
extension Mailbox {
|
extension Mailbox {
|
||||||
static var dummy: Mailbox {
|
static var dummy: Mailbox {
|
||||||
.init(noHandle: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,8 +126,8 @@ struct AppProtectionSelectionScreen: View {
|
|||||||
.init(type: .faceID, isSelected: true),
|
.init(type: .faceID, isSelected: true),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
appSettingsRepository: MailSession(noHandle: .init()),
|
appSettingsRepository: MailSession(noPointer: .init()),
|
||||||
appProtectionConfigurator: MailSession(noHandle: .init())
|
appProtectionConfigurator: MailSession(noPointer: .init())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ struct AppSettingsScreen: View {
|
|||||||
NavigationStack {
|
NavigationStack {
|
||||||
AppSettingsScreen(
|
AppSettingsScreen(
|
||||||
state: .initial(isDiscreetAppIconEnabled: false),
|
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 {
|
func emptyScreenVariant(isUnreadFilterOn: Bool) -> NoResultsView.Variant {
|
||||||
switch self {
|
switch self {
|
||||||
case .inbox, .allDrafts, .allSent, .sent, .trash, .spam, .allMail, .archive, .drafts, .starred, .scheduled,
|
case .inbox, .allDrafts, .allSent, .sent, .trash, .spam, .allMail, .archive, .drafts, .starred, .scheduled,
|
||||||
.almostAllMail, .snoozed, .categorySocial, .categoryPromotions, .catergoryUpdates, .categoryForums,
|
.almostAllMail, .snoozed, .categorySocial, .categoryPromotions, .categoryUpdates, .categoryForums,
|
||||||
.categoryDefault, .blocked, .pinned:
|
.categoryDefault, .blocked, .pinned, .categoryNewsletter, .categoryTransactions:
|
||||||
.mailbox(isUnreadFilterOn: isUnreadFilterOn)
|
.mailbox(isUnreadFilterOn: isUnreadFilterOn)
|
||||||
case .outbox:
|
case .outbox:
|
||||||
.outbox
|
.outbox
|
||||||
|
|||||||
@@ -197,5 +197,5 @@ struct ReportProblemScreen: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#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)
|
AppLogger.log(error: error, category: .mailbox)
|
||||||
let isLastPage = await !searchScrollerHasMore()
|
let isLastPage = await !searchScrollerHasMore()
|
||||||
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
paginatedDataSource.handle(update: .init(isLastPage: isLastPage, value: .error(error), completion: nil))
|
||||||
|
case .categoryViewChanged:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ private extension SettingsPreference {
|
|||||||
#Preview {
|
#Preview {
|
||||||
NavigationStack {
|
NavigationStack {
|
||||||
SettingsScreen(
|
SettingsScreen(
|
||||||
mailUserSession: MailUserSession(noHandle: .init()),
|
mailUserSession: MailUserSession(noPointer: .init()),
|
||||||
accountAuthCoordinator: .mock(),
|
accountAuthCoordinator: .mock(),
|
||||||
upsellCoordinator: .init(
|
upsellCoordinator: .init(
|
||||||
mailUserSession: .dummy,
|
mailUserSession: .dummy,
|
||||||
|
|||||||
@@ -232,8 +232,6 @@ struct SidebarScreen: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private func upsellSidebarItem(item: SidebarItem, upsellType: UpsellType) -> some View {
|
private func upsellSidebarItem(item: SidebarItem, upsellType: UpsellType) -> some View {
|
||||||
let planName = UpsellConfiguration.mail.humanReadableUpsoldPlanName
|
|
||||||
|
|
||||||
SidebarItemButton(
|
SidebarItemButton(
|
||||||
item: item,
|
item: item,
|
||||||
isTappable: isButtonTappable,
|
isTappable: isButtonTappable,
|
||||||
@@ -241,7 +239,7 @@ struct SidebarScreen: View {
|
|||||||
content: {
|
content: {
|
||||||
HStack(spacing: .zero) {
|
HStack(spacing: .zero) {
|
||||||
sidebarItemImage(icon: upsellType.icon.image, isSelected: false, renderingMode: .original)
|
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()
|
Spacer()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -370,11 +368,11 @@ struct SidebarScreen: View {
|
|||||||
.accessibilityIdentifier(SidebarScreenIdentifiers.badgeIcon)
|
.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)
|
Text(name)
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.fontWeight(isSelected ? .bold : .regular)
|
.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)
|
.lineLimit(1)
|
||||||
.accessibilityIdentifier(SidebarScreenIdentifiers.textItem)
|
.accessibilityIdentifier(SidebarScreenIdentifiers.textItem)
|
||||||
}
|
}
|
||||||
@@ -455,24 +453,25 @@ private extension SidebarOtherItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension UpsellType {
|
private extension UpsellType {
|
||||||
|
var planName: String {
|
||||||
|
switch self {
|
||||||
|
case .mailPlus:
|
||||||
|
"Mail Plus"
|
||||||
|
case .unlimited:
|
||||||
|
"Unlimited"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var icon: ImageResource {
|
var icon: ImageResource {
|
||||||
switch self {
|
switch self {
|
||||||
case .mailPlus, .unlimited:
|
case .mailPlus:
|
||||||
DS.Icon.icDiamond
|
DS.Icon.icDiamond
|
||||||
|
case .unlimited:
|
||||||
|
DS.Icon.icInfinity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func title(planName: String) -> String {
|
var title: String {
|
||||||
switch self {
|
L10n.Sidebar.upgrade(to: planName).string
|
||||||
case .mailPlus, .unlimited:
|
|
||||||
L10n.Sidebar.upgrade(to: planName).string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tint: Color? {
|
|
||||||
switch self {
|
|
||||||
case .mailPlus, .unlimited:
|
|
||||||
nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,6 +90,8 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
|
|||||||
if case .eligible(let upsellType) = upsellEligibility {
|
if case .eligible(let upsellType) = upsellEligibility {
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
upsellButton(for: upsellType)
|
upsellButton(for: upsellType)
|
||||||
|
.frame(width: 26, height: 26)
|
||||||
|
.clipShape(.circle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ToolbarItem(placement: .topBarTrailing) {
|
ToolbarItem(placement: .topBarTrailing) {
|
||||||
@@ -111,10 +113,10 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
|
|||||||
if !selectionMode.hasItems {
|
if !selectionMode.hasItems {
|
||||||
ToolbarItemGroup(placement: .topBarTrailing) {
|
ToolbarItemGroup(placement: .topBarTrailing) {
|
||||||
HStack(spacing: DS.Spacing.standard) {
|
HStack(spacing: DS.Spacing.standard) {
|
||||||
|
searchButton
|
||||||
if case .eligible(let upsellType) = upsellEligibility {
|
if case .eligible(let upsellType) = upsellEligibility {
|
||||||
upsellButton(for: upsellType)
|
upsellButton(for: upsellType)
|
||||||
}
|
}
|
||||||
searchButton
|
|
||||||
avatarView()
|
avatarView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -141,15 +143,35 @@ struct MainToolbar<AvatarView: View>: ViewModifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
private func upsellButton(for upsellType: UpsellType) -> some View {
|
private func upsellButton(for upsellType: UpsellType) -> some View {
|
||||||
Button(L10n.MainToolbar.upgrade, image: upsellType.icon) {
|
switch upsellType {
|
||||||
Task {
|
case .mailPlus:
|
||||||
do {
|
Button(L10n.MainToolbar.upgrade, image: upsellType.icon) {
|
||||||
let upsellScreenModel = try await upsellCoordinator.presentUpsellScreen(entryPoint: .mailboxTopBar, upsellType: upsellType)
|
performUpsellAction(upsellType: upsellType)
|
||||||
onEvent(.onUpsell(upsellScreenModel))
|
}
|
||||||
} catch {
|
case .unlimited:
|
||||||
toastStateStore.present(toast: .error(message: error.localizedDescription))
|
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 {
|
private extension UpsellType {
|
||||||
var icon: ImageResource {
|
var icon: ImageResource {
|
||||||
switch self {
|
switch self {
|
||||||
case .mailPlus, .unlimited:
|
case .mailPlus:
|
||||||
DS.Icon.icBrandProtonMailUpsellBlackAndWhite
|
DS.Icon.icBrandProtonMailUpsellBlackAndWhite
|
||||||
|
case .unlimited:
|
||||||
|
DS.Icon.icBrandProtonUnlimitedUpsellHeader
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ final class MailboxStub: Mailbox, @unchecked Sendable {
|
|||||||
|
|
||||||
init(viewMode: ViewMode) {
|
init(viewMode: ViewMode) {
|
||||||
self._viewMode = viewMode
|
self._viewMode = viewMode
|
||||||
super.init(noHandle: .init())
|
super.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init(unsafeFromHandle handle: UInt64) {
|
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
|
||||||
fatalError("init(unsafeFromHandle:) has not been implemented")
|
fatalError("init(unsafeFromRawPointer:) has not been implemented")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,6 +67,6 @@ final class SidebarSpy: SidebarProtocol, @unchecked Sendable {
|
|||||||
let mappedIndex: CallbackIndex = lastIndex == -1 ? .folder : .init(rawValue: lastIndex + 1)!
|
let mappedIndex: CallbackIndex = lastIndex == -1 ? .folder : .init(rawValue: lastIndex + 1)!
|
||||||
spiedWatchers[mappedIndex] = callback
|
spiedWatchers[mappedIndex] = callback
|
||||||
|
|
||||||
return .ok(WatchHandleDummy(noHandle: .init()))
|
return .ok(WatchHandleDummy(noPointer: .init()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
extension MailUserSession {
|
extension MailUserSession {
|
||||||
static var dummy: MailUserSession {
|
static var dummy: MailUserSession {
|
||||||
.init(noHandle: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
extension Mailbox {
|
extension Mailbox {
|
||||||
static var dummy: Mailbox {
|
static var dummy: Mailbox {
|
||||||
.init(noHandle: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ final class MailboxItemCellSnapshotTests {
|
|||||||
("expiration_time_message", .makeSimpleMessage(type: .expirationTime)),
|
("expiration_time_message", .makeSimpleMessage(type: .expirationTime)),
|
||||||
])
|
])
|
||||||
func mailboxItemCell(testName: String, model: MailboxItemCellUIModel) {
|
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
|
@MainActor
|
||||||
class SettingsScreenSnapshotTests: BaseTestCase {
|
class SettingsScreenSnapshotTests: BaseTestCase {
|
||||||
func testSettingsScreenLayoutsCorrectOnIphoneX() {
|
func testSettingsScreenLayoutsCorrectOnIphoneX() {
|
||||||
let store = AppAppearanceStore(mailSession: { MailSession(noHandle: .init()) })
|
let store = AppAppearanceStore(mailSession: { MailSession(noPointer: .init()) })
|
||||||
let mailUserSession = MailUserSessionSpy(id: "")
|
let mailUserSession = MailUserSessionSpy(id: "")
|
||||||
mailUserSession.stubbedAccountDetails = .testData
|
mailUserSession.stubbedAccountDetails = .testData
|
||||||
mailUserSession.stubbedUser = .testData
|
mailUserSession.stubbedUser = .testData
|
||||||
|
|||||||
@@ -37,9 +37,10 @@ final class SidebarScreenSnapshotTests {
|
|||||||
createFolder: .createFolder
|
createFolder: .createFolder
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test(arguments: [UIUserInterfaceStyle.light, .dark])
|
@Test(arguments: [UIUserInterfaceStyle.light, .dark], [UpsellType.mailPlus, .unlimited])
|
||||||
func testSidebarWithDataLayoutsCorrectOnIphoneX(style: UIUserInterfaceStyle) {
|
func testSidebarWithDataLayoutsCorrectOnIphoneX(style: UIUserInterfaceStyle, upsellType: UpsellType) {
|
||||||
var state = self.state
|
var state = self.state
|
||||||
|
state.upsell = .upsell(upsellType)
|
||||||
|
|
||||||
state.folders = [SidebarCustomFolder.topSecretFolder].map(\.sidebarFolder)
|
state.folders = [SidebarCustomFolder.topSecretFolder].map(\.sidebarFolder)
|
||||||
state.system = [PMSystemLabel.inbox, .sent, .outbox].compactMap(\.sidebarSystemFolder)
|
state.system = [PMSystemLabel.inbox, .sent, .outbox].compactMap(\.sidebarSystemFolder)
|
||||||
@@ -48,7 +49,7 @@ final class SidebarScreenSnapshotTests {
|
|||||||
let screenModel = SidebarModel(
|
let screenModel = SidebarModel(
|
||||||
state: state,
|
state: state,
|
||||||
sidebar: SidebarSpy(),
|
sidebar: SidebarSpy(),
|
||||||
upsellEligibilityPublisher: .init(constant: .eligible(.mailPlus))
|
upsellEligibilityPublisher: .init(constant: .eligible(upsellType))
|
||||||
)
|
)
|
||||||
let sidebarScreen = SidebarScreen(
|
let sidebarScreen = SidebarScreen(
|
||||||
screenModel: screenModel,
|
screenModel: screenModel,
|
||||||
@@ -57,7 +58,7 @@ final class SidebarScreenSnapshotTests {
|
|||||||
)
|
)
|
||||||
.environmentObject(AppUIStateStore(sidebarState: .init(zIndex: .zero, visibleWidth: 320)))
|
.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])
|
@Test(arguments: [UIUserInterfaceStyle.light, .dark])
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 149 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 156 KiB |
@@ -25,12 +25,24 @@ import proton_app_uniffi
|
|||||||
@testable import InboxIAP
|
@testable import InboxIAP
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
@Suite(.disabled("Tests are failing on CI only, needs more investigation."))
|
|
||||||
struct UpsellScreenSnapshotTests {
|
struct UpsellScreenSnapshotTests {
|
||||||
|
enum SelectedCycle: String {
|
||||||
|
case yearly
|
||||||
|
case monthly
|
||||||
|
|
||||||
|
var lengthInMonths: Int {
|
||||||
|
switch self {
|
||||||
|
case .yearly: 12
|
||||||
|
case .monthly: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct TestCase {
|
struct TestCase {
|
||||||
let label: String
|
let label: String
|
||||||
let config: ViewImageConfig
|
let config: ViewImageConfig
|
||||||
let upsellType: UpsellType
|
let upsellType: UpsellType
|
||||||
|
let selectedCycle: SelectedCycle
|
||||||
}
|
}
|
||||||
|
|
||||||
nonisolated private static let testCases: [TestCase] = {
|
nonisolated private static let testCases: [TestCase] = {
|
||||||
@@ -42,15 +54,19 @@ struct UpsellScreenSnapshotTests {
|
|||||||
]
|
]
|
||||||
|
|
||||||
let upsellTypes: [UpsellType] = [.mailPlus, .unlimited]
|
let upsellTypes: [UpsellType] = [.mailPlus, .unlimited]
|
||||||
|
let selectedCycles: [SelectedCycle] = [.yearly, .monthly]
|
||||||
|
|
||||||
return orientations.flatMap { orientation in
|
return orientations.flatMap { orientation in
|
||||||
devices.flatMap { device in
|
devices.flatMap { device in
|
||||||
upsellTypes.map { upsellType in
|
upsellTypes.flatMap { upsellType in
|
||||||
.init(
|
selectedCycles.map { selectedCycle in
|
||||||
label: "\(device.label)_\(orientation)_\(upsellType.label)",
|
.init(
|
||||||
config: device.configFactory(orientation),
|
label: "\(device.label)_\(orientation)_\(upsellType.label)_\(selectedCycle.rawValue)",
|
||||||
upsellType: upsellType
|
config: device.configFactory(orientation),
|
||||||
)
|
upsellType: upsellType,
|
||||||
|
selectedCycle: selectedCycle
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,12 +74,20 @@ struct UpsellScreenSnapshotTests {
|
|||||||
|
|
||||||
@Test(arguments: testCases)
|
@Test(arguments: testCases)
|
||||||
func upsellScreen(testCase: TestCase) {
|
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)
|
let viewController = UIHostingController(rootView: sut)
|
||||||
|
viewController.view.backgroundColor = .black
|
||||||
|
viewController.overrideUserInterfaceStyle = .dark
|
||||||
|
|
||||||
let strategy: Snapshotting<UIViewController, UIImage> = .image(
|
let strategy: Snapshotting<UIViewController, UIImage> = .image(
|
||||||
on: testCase.config,
|
on: testCase.config,
|
||||||
drawHierarchyInKeyWindow: true
|
drawHierarchyInKeyWindow: true,
|
||||||
|
precision: 0.99
|
||||||
)
|
)
|
||||||
|
|
||||||
assertSnapshot(of: viewController, as: strategy, named: testCase.label)
|
assertSnapshot(of: viewController, as: strategy, named: testCase.label)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 995 KiB |
|
After Width: | Height: | Size: 995 KiB |
|
After Width: | Height: | Size: 998 KiB |
|
Before Width: | Height: | Size: 995 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
Before Width: | Height: | Size: 812 KiB |
|
After Width: | Height: | Size: 818 KiB |
|
After Width: | Height: | Size: 818 KiB |
|
Before Width: | Height: | Size: 812 KiB |
|
After Width: | Height: | Size: 885 KiB |
|
After Width: | Height: | Size: 887 KiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 165 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 149 KiB |
|
After Width: | Height: | Size: 142 KiB |
@@ -35,7 +35,7 @@ final class SceneDelegateTests: BaseTestCase {
|
|||||||
sut = .init()
|
sut = .init()
|
||||||
mailSessionSpy = .init()
|
mailSessionSpy = .init()
|
||||||
sut.appProtectionStore = .init(mailSession: { self.mailSessionSpy })
|
sut.appProtectionStore = .init(mailSession: { self.mailSessionSpy })
|
||||||
sut.mailSessionFactory = { .init(noHandle: .init()) }
|
sut.mailSessionFactory = { .init(noPointer: .init()) }
|
||||||
sut.checkAutoLockSetting = { completion in completion(self.shouldAutoLockStub) }
|
sut.checkAutoLockSetting = { completion in completion(self.shouldAutoLockStub) }
|
||||||
sut.transitionAnimation = { _, _, _, animation, completion in
|
sut.transitionAnimation = { _, _, _, animation, completion in
|
||||||
animation?()
|
animation?()
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ final class DeviceTokenRegistrarTests {
|
|||||||
var lastCreatedHandle: RegisterDeviceTaskHandleSpy?
|
var lastCreatedHandle: RegisterDeviceTaskHandleSpy?
|
||||||
|
|
||||||
mailSession.stubbedRegisterDeviceTaskHandleFactory = {
|
mailSession.stubbedRegisterDeviceTaskHandleFactory = {
|
||||||
let newHandle = RegisterDeviceTaskHandleSpy(noHandle: .init())
|
let newHandle = RegisterDeviceTaskHandleSpy(noPointer: .init())
|
||||||
lastCreatedHandle = newHandle
|
lastCreatedHandle = newHandle
|
||||||
return newHandle
|
return newHandle
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ final class DraftPresenterTests: BaseTestCase, @unchecked Sendable {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func testOpenDraftWithContact_ItCreatesEmptyDraftAddRecipientAndOpensDraft() async throws {
|
func testOpenDraftWithContact_ItCreatesEmptyDraftAddRecipientAndOpensDraft() async throws {
|
||||||
let draftSpy = DraftSpy(noHandle: .init())
|
let draftSpy = DraftSpy(noPointer: .init())
|
||||||
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
|
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
|
||||||
|
|
||||||
var capturedDraftToPresent: [DraftToPresent] = []
|
var capturedDraftToPresent: [DraftToPresent] = []
|
||||||
@@ -123,7 +123,7 @@ final class DraftPresenterTests: BaseTestCase, @unchecked Sendable {
|
|||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
func testOpenDraftWithContactGroup_ItCreatesEmptyDraftAddGroupAndOpensDraft() async throws {
|
func testOpenDraftWithContactGroup_ItCreatesEmptyDraftAddGroupAndOpensDraft() async throws {
|
||||||
let draftSpy = DraftSpy(noHandle: .init())
|
let draftSpy = DraftSpy(noPointer: .init())
|
||||||
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
|
sut = makeSUT(stubbedNewDraftResult: .ok(draftSpy))
|
||||||
|
|
||||||
var capturedDraftToPresent: [DraftToPresent] = []
|
var capturedDraftToPresent: [DraftToPresent] = []
|
||||||
@@ -292,13 +292,13 @@ extension DraftPresenterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private extension Draft {
|
private extension Draft {
|
||||||
static var dummyDraft: Draft { .init(noHandle: .init()) }
|
static var dummyDraft: Draft { .init(noPointer: .init()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class DraftSpy: Draft, @unchecked Sendable {
|
private class DraftSpy: Draft, @unchecked Sendable {
|
||||||
let toRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
|
let toRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
|
||||||
let ccRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
|
let ccRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
|
||||||
let bccRecipientsCalls: ComposerRecipientListSpy = .init(noHandle: .init())
|
let bccRecipientsCalls: ComposerRecipientListSpy = .init(noPointer: .init())
|
||||||
private(set) var setSubjectCalls: [String] = []
|
private(set) var setSubjectCalls: [String] = []
|
||||||
private(set) var setBodyCalls: [String] = []
|
private(set) var setBodyCalls: [String] = []
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ final class LabelAsSheetModelTests {
|
|||||||
private var invokedAvailableActionsWithConversationIDs: [ID] = []
|
private var invokedAvailableActionsWithConversationIDs: [ID] = []
|
||||||
private var invokedDismissCount = 0
|
private var invokedDismissCount = 0
|
||||||
private var stubbedLabelAsActions: [LabelAsAction] = []
|
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 toastStateStore = ToastStateStore(initialState: .initial)
|
||||||
private var invokedLabelMessage: [LabelAsExecutedWithData] = []
|
private var invokedLabelMessage: [LabelAsExecutedWithData] = []
|
||||||
@@ -175,7 +175,7 @@ final class LabelAsSheetModelTests {
|
|||||||
private func sut(ids: [ID], type: MailboxItemType) -> LabelAsSheetModel {
|
private func sut(ids: [ID], type: MailboxItemType) -> LabelAsSheetModel {
|
||||||
LabelAsSheetModel(
|
LabelAsSheetModel(
|
||||||
input: .init(sheetType: .labelAs, ids: ids, mailboxItem: type.mailboxItem),
|
input: .init(sheetType: .labelAs, ids: ids, mailboxItem: type.mailboxItem),
|
||||||
mailbox: .init(noHandle: .init()),
|
mailbox: .init(noPointer: .init()),
|
||||||
availableLabelAsActions: .init(
|
availableLabelAsActions: .init(
|
||||||
message: { _, ids in
|
message: { _, ids in
|
||||||
self.invokedAvailableActionsWithMessagesIDs = ids
|
self.invokedAvailableActionsWithMessagesIDs = ids
|
||||||
@@ -220,7 +220,7 @@ final class LabelAsSheetModelTests {
|
|||||||
itemType: MailboxItemType,
|
itemType: MailboxItemType,
|
||||||
spyToVerify: () -> [LabelAsExecutedWithData]
|
spyToVerify: () -> [LabelAsExecutedWithData]
|
||||||
) async throws {
|
) async throws {
|
||||||
let undoSpy = UndoSpy(noHandle: .init())
|
let undoSpy = UndoSpy(noPointer: .init())
|
||||||
let selectedLabelID: ID = .init(value: 2)
|
let selectedLabelID: ID = .init(value: 2)
|
||||||
let partiallySelectedLabelID: ID = .init(value: 4)
|
let partiallySelectedLabelID: ID = .init(value: 4)
|
||||||
stubbedLabelAsActions = [
|
stubbedLabelAsActions = [
|
||||||
@@ -257,7 +257,7 @@ final class LabelAsSheetModelTests {
|
|||||||
spyToVerify: () -> [LabelAsExecutedWithData],
|
spyToVerify: () -> [LabelAsExecutedWithData],
|
||||||
expectToastMessage: LocalizedStringResource
|
expectToastMessage: LocalizedStringResource
|
||||||
) async throws {
|
) async throws {
|
||||||
let undoSpy = UndoSpy(noHandle: .init())
|
let undoSpy = UndoSpy(noPointer: .init())
|
||||||
let selectedLabelID: ID = .init(value: 2)
|
let selectedLabelID: ID = .init(value: 2)
|
||||||
let partiallySelectedLabelID: ID = .init(value: 4)
|
let partiallySelectedLabelID: ID = .init(value: 4)
|
||||||
stubbedLabelAsActions = [
|
stubbedLabelAsActions = [
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ class ListActionsToolbarStoreTests {
|
|||||||
func action_WhenMoveToInboxIsTappedUndoIsAvailbleAndTapped_ItTriggersUndoAndDismissesToast() async throws {
|
func action_WhenMoveToInboxIsTappedUndoIsAvailbleAndTapped_ItTriggersUndoAndDismissesToast() async throws {
|
||||||
let ids: [ID] = [.init(value: 7), .init(value: 77)]
|
let ids: [ID] = [.init(value: 7), .init(value: 77)]
|
||||||
let systemFolder = MovableSystemFolderAction.testInbox
|
let systemFolder = MovableSystemFolderAction.testInbox
|
||||||
let undoSpy = UndoSpy(noHandle: .init())
|
let undoSpy = UndoSpy(noPointer: .init())
|
||||||
let viewMode = ViewMode.messages
|
let viewMode = ViewMode.messages
|
||||||
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
|
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
|
||||||
sut = makeSUT(viewMode: viewMode)
|
sut = makeSUT(viewMode: viewMode)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ final class MoveToActionPerformerTests: BaseTestCase {
|
|||||||
super.setUp()
|
super.setUp()
|
||||||
|
|
||||||
sut = .init(
|
sut = .init(
|
||||||
mailbox: .init(noHandle: .init()),
|
mailbox: .init(noPointer: .init()),
|
||||||
moveToActions: .init(
|
moveToActions: .init(
|
||||||
moveMessagesTo: { [unowned self] _, _, _ in stubbedResult },
|
moveMessagesTo: { [unowned self] _, _, _ in stubbedResult },
|
||||||
moveConversationsTo: { [unowned self] _, _, _ in stubbedResult }
|
moveConversationsTo: { [unowned self] _, _, _ in stubbedResult }
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ final class MoveToSheetStateStoreTests {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
func action_WhenInboxIsTappedUndoIsAvailableAndTapped_ItTriggersUndoAndDismissesToast() async throws {
|
func action_WhenInboxIsTappedUndoIsAvailableAndTapped_ItTriggersUndoAndDismissesToast() async throws {
|
||||||
let undoSpy = UndoSpy(noHandle: .init())
|
let undoSpy = UndoSpy(noPointer: .init())
|
||||||
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
|
moveToActionsSpy.stubbedMoveMessagesToOkResult = undoSpy
|
||||||
|
|
||||||
let sut = sut(
|
let sut = sut(
|
||||||
@@ -155,7 +155,7 @@ final class MoveToSheetStateStoreTests {
|
|||||||
.init(
|
.init(
|
||||||
state: .initial,
|
state: .initial,
|
||||||
input: input,
|
input: input,
|
||||||
mailbox: .init(noHandle: .init()),
|
mailbox: .init(noPointer: .init()),
|
||||||
availableMoveToActions: .init(
|
availableMoveToActions: .init(
|
||||||
message: { _, ids in
|
message: { _, ids in
|
||||||
self.invokedAvailableActionsWithMessagesIDs = ids
|
self.invokedAvailableActionsWithMessagesIDs = ids
|
||||||
|
|||||||
@@ -566,12 +566,12 @@ private final class DecryptedMessageSpy: DecryptedMessage, @unchecked Sendable {
|
|||||||
|
|
||||||
init(stubbedOptions: TransformOpts) {
|
init(stubbedOptions: TransformOpts) {
|
||||||
self.stubbedOptions = stubbedOptions
|
self.stubbedOptions = stubbedOptions
|
||||||
super.init(noHandle: .init())
|
super.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init(unsafeFromHandle handle: UInt64) {
|
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
|
||||||
fatalError("init(unsafeFromHandle:) has not been implemented")
|
fatalError("init(unsafeFromRawPointer:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
private(set) var bodyWithOptionsCalls: [TransformOpts] = []
|
private(set) var bodyWithOptionsCalls: [TransformOpts] = []
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ final class MessageAddressActionViewStateStoreTests {
|
|||||||
senderUnblocker: .init(
|
senderUnblocker: .init(
|
||||||
mailbox: .dummy,
|
mailbox: .dummy,
|
||||||
wrapper: .init(
|
wrapper: .init(
|
||||||
messageBody: { _, _ in .ok(.init(noHandle: .init())) },
|
messageBody: { _, _ in .ok(.init(noPointer: .init())) },
|
||||||
markMessageHam: { _, _ in .ok },
|
markMessageHam: { _, _ in .ok },
|
||||||
unblockSender: { _, emailAddress in
|
unblockSender: { _, emailAddress in
|
||||||
await self.unblockSpy.result(for: emailAddress)
|
await self.unblockSpy.result(for: emailAddress)
|
||||||
|
|||||||
@@ -235,12 +235,12 @@ class BackgroundExecutionHandleStub: BackgroundExecutionHandle, @unchecked Senda
|
|||||||
private(set) var abortCalls: [Bool] = []
|
private(set) var abortCalls: [Bool] = []
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
super.init(noHandle: .init())
|
super.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init(unsafeFromHandle handle: UInt64) {
|
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
|
||||||
fatalError("init(unsafeFromHandle:) has not been implemented")
|
fatalError("init(unsafeFromRawPointer:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - BackgroundExecutionHandle
|
// MARK: - BackgroundExecutionHandle
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ struct ComposerLoadingView: View {
|
|||||||
ComposerScreen(
|
ComposerScreen(
|
||||||
draft: .emptyMock,
|
draft: .emptyMock,
|
||||||
draftOrigin: .new,
|
draftOrigin: .new,
|
||||||
dependencies: .init(contactProvider: .mockInstance, userSession: .init(noHandle: .init())),
|
dependencies: .init(contactProvider: .mockInstance, userSession: .init(noPointer: .init())),
|
||||||
onDismiss: { _ in }
|
onDismiss: { _ in }
|
||||||
)
|
)
|
||||||
.environmentObject(toastStateStore)
|
.environmentObject(toastStateStore)
|
||||||
|
|||||||
@@ -307,6 +307,6 @@ final class MockAttachmentList: AttachmentListProtocol, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func watcherStream() async -> AttachmentListWatcherStreamResult {
|
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 {
|
private extension MailUserSession {
|
||||||
static func empty() -> MailUserSession {
|
static func empty() -> MailUserSession {
|
||||||
MailUserSession(noHandle: .init())
|
MailUserSession(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,12 +150,12 @@ private class ContactSuggestionsStub: ContactSuggestions, @unchecked Sendable {
|
|||||||
|
|
||||||
init(all: [ContactSuggestion]) {
|
init(all: [ContactSuggestion]) {
|
||||||
_all = all
|
_all = all
|
||||||
super.init(noHandle: .init())
|
super.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init(unsafeFromHandle handle: UInt64) {
|
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
|
||||||
fatalError("init(unsafeFromHandle:) has not been implemented")
|
fatalError("init(unsafeFromRawPointer:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func all() -> [ContactSuggestion] {
|
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 {
|
public extension ResolveMessageIdResult {
|
||||||
func get() throws(ActionError) -> Id {
|
func get() throws(ActionError) -> Id {
|
||||||
switch self {
|
switch self {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ public struct ContactsScreen: View {
|
|||||||
#Preview {
|
#Preview {
|
||||||
ContactsScreen(
|
ContactsScreen(
|
||||||
apiConfig: .debugPreview,
|
apiConfig: .debugPreview,
|
||||||
mailUserSession: .init(noHandle: .init()),
|
mailUserSession: .init(noPointer: .init()),
|
||||||
contactsProvider: .previewInstance(),
|
contactsProvider: .previewInstance(),
|
||||||
contactsWatcher: .previewInstance(),
|
contactsWatcher: .previewInstance(),
|
||||||
draftPresenter: ContactsDraftPresenterDummy()
|
draftPresenter: ContactsDraftPresenterDummy()
|
||||||
|
|||||||
@@ -19,6 +19,6 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
extension ContactsWatcher {
|
extension ContactsWatcher {
|
||||||
static func previewInstance() -> Self {
|
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 {
|
extension MailUserSession {
|
||||||
static func testInstance() -> MailUserSession {
|
static func testInstance() -> MailUserSession {
|
||||||
.init(noHandle: .init())
|
.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ final class ContactSuggestionsRepositoryTests {
|
|||||||
let result = ContactSuggestionsStub(all: self.stubbedAllContacts)
|
let result = ContactSuggestionsStub(all: self.stubbedAllContacts)
|
||||||
return .ok(result)
|
return .ok(result)
|
||||||
}),
|
}),
|
||||||
mailUserSession: MailUserSession(noHandle: .init())
|
mailUserSession: MailUserSession(noPointer: .init())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,12 +349,12 @@ private class ContactSuggestionsStub: ContactSuggestions, @unchecked Sendable {
|
|||||||
|
|
||||||
init(all: [ContactSuggestion]) {
|
init(all: [ContactSuggestion]) {
|
||||||
_all = all
|
_all = all
|
||||||
super.init(noHandle: .init())
|
super.init(noPointer: .init())
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
required init(unsafeFromHandle handle: UInt64) {
|
required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) {
|
||||||
fatalError("init(unsafeFromHandle:) has not been implemented")
|
fatalError("init(unsafeFromRawPointer:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override func all() -> [ContactSuggestion] {
|
override func all() -> [ContactSuggestion] {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import proton_app_uniffi
|
|||||||
final class ContactViewFactoryTests {
|
final class ContactViewFactoryTests {
|
||||||
let sut = ContactViewFactory(
|
let sut = ContactViewFactory(
|
||||||
apiConfig: .testData,
|
apiConfig: .testData,
|
||||||
mailUserSession: .init(noHandle: .init()),
|
mailUserSession: .init(noPointer: .init()),
|
||||||
draftPresenter: ContactsDraftPresenterDummy()
|
draftPresenter: ContactsDraftPresenterDummy()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -627,7 +627,7 @@ final class ContactsStateStoreTests {
|
|||||||
},
|
},
|
||||||
contactsWatcher: .init(watch: { [unowned self] _, callback in
|
contactsWatcher: .init(watch: { [unowned self] _, callback in
|
||||||
watchContactsCallback = callback
|
watchContactsCallback = callback
|
||||||
return WatchContactListResult.ok(.init(contactList: [], handle: .init(noHandle: .init())))
|
return WatchContactListResult.ok(.init(contactList: [], handle: .init(noPointer: .init())))
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
makeContactsLiveQuery: { [unowned self] in
|
makeContactsLiveQuery: { [unowned self] in
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import proton_app_uniffi
|
|||||||
|
|
||||||
final class GroupedContactsRepositoryTests {
|
final class GroupedContactsRepositoryTests {
|
||||||
private lazy var sut: GroupedContactsRepository = .init(
|
private lazy var sut: GroupedContactsRepository = .init(
|
||||||
mailUserSession: MailUserSession(noHandle: .init()),
|
mailUserSession: MailUserSession(noPointer: .init()),
|
||||||
contactsProvider: .init(allContacts: { _ in .ok(self.stubbedContacts) })
|
contactsProvider: .init(allContacts: { _ in .ok(self.stubbedContacts) })
|
||||||
)
|
)
|
||||||
private var stubbedContacts: [GroupedContacts] = []
|
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 {
|
public extension ConversationScrollerChangeFilterResult {
|
||||||
func get() throws(MailScrollerError) {
|
func get() throws(MailScrollerError) {
|
||||||
switch self {
|
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 {
|
public extension MessageScrollerChangeFilterResult {
|
||||||
func get() throws(MailScrollerError) {
|
func get() throws(MailScrollerError) {
|
||||||
switch self {
|
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 {
|
public extension ResolveSystemLabelByIdResult {
|
||||||
func get() throws(ProtonError) -> SystemLabel? {
|
func get() throws(ProtonError) -> SystemLabel? {
|
||||||
switch self {
|
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 {
|
public extension UpdateNextMessageOnMoveResult {
|
||||||
func get() throws(UserSessionError) {
|
func get() throws(UserSessionError) {
|
||||||
switch self {
|
switch self {
|
||||||
|
|||||||
@@ -4852,7 +4852,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"sv" : {
|
||||||
@@ -5001,7 +5001,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"sv" : {
|
||||||
@@ -5150,7 +5150,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"tr" : {
|
||||||
@@ -5293,7 +5293,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"sv" : {
|
||||||
@@ -5442,7 +5442,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"sv" : {
|
||||||
@@ -5591,7 +5591,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"sv" : {
|
||||||
@@ -5883,7 +5883,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"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" : {
|
"tr" : {
|
||||||
@@ -7016,7 +7016,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Šifrirano brez dostopa za preverjenega prejemnika"
|
"value" : "Šifrirano brez dostopa s preverjenim prejemnikom"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sv" : {
|
"sv" : {
|
||||||
@@ -7159,7 +7159,7 @@
|
|||||||
"sl" : {
|
"sl" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
"state" : "translated",
|
"state" : "translated",
|
||||||
"value" : "Šifrirano brez dostopa za preverjene prejemnike"
|
"value" : "Šifrirano brez dostopa s preverjenimi prejemniki"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sv" : {
|
"sv" : {
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ public extension DS.Icon {
|
|||||||
public extension DS.Icon {
|
public extension DS.Icon {
|
||||||
static let icBrandProtonMailUpsell = ImageResource.icBrandProtonMailUpsell
|
static let icBrandProtonMailUpsell = ImageResource.icBrandProtonMailUpsell
|
||||||
static let icBrandProtonMailUpsellBlackAndWhite = ImageResource.icBrandProtonMailUpsellBw
|
static let icBrandProtonMailUpsellBlackAndWhite = ImageResource.icBrandProtonMailUpsellBw
|
||||||
|
static let icBrandProtonUnlimitedUpsellHeader = ImageResource.icBrandProtonUnlimitedUpsellHeader
|
||||||
static let upsellBlackFridayHeaderButtonWave1 = ImageResource.upsellBlackFridayHeaderButtonWave1
|
static let upsellBlackFridayHeaderButtonWave1 = ImageResource.upsellBlackFridayHeaderButtonWave1
|
||||||
static let upsellBlackFridayHeaderButtonWave2 = ImageResource.upsellBlackFridayHeaderButtonWave2
|
static let upsellBlackFridayHeaderButtonWave2 = ImageResource.upsellBlackFridayHeaderButtonWave2
|
||||||
static let upsellBlackFridaySidebarItemWave1 = ImageResource.upsellBlackFridaySidebarItemWave1
|
static let upsellBlackFridaySidebarItemWave1 = ImageResource.upsellBlackFridaySidebarItemWave1
|
||||||
@@ -98,6 +99,9 @@ public extension DS.Icon {
|
|||||||
public extension DS.Icon {
|
public extension DS.Icon {
|
||||||
static let icCode = ImageResource.icCode
|
static let icCode = ImageResource.icCode
|
||||||
static let icDiamond = ImageResource.icDiamond
|
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 icEnvelopeDot = ImageResource.icEnvelopeDot
|
||||||
static let icEnvelopeOpen = ImageResource.icEnvelopeOpen
|
static let icEnvelopeOpen = ImageResource.icEnvelopeOpen
|
||||||
static let icFileLines = ImageResource.icFileLines
|
static let icFileLines = ImageResource.icFileLines
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public extension DS.Images {
|
|||||||
public static let logoMobileSignature = ImageResource.upsellLogoMobileSignature
|
public static let logoMobileSignature = ImageResource.upsellLogoMobileSignature
|
||||||
public static let logoScheduleSend = ImageResource.upsellLogoScheduleSend
|
public static let logoScheduleSend = ImageResource.upsellLogoScheduleSend
|
||||||
public static let logoSnooze = ImageResource.upsellLogoSnooze
|
public static let logoSnooze = ImageResource.upsellLogoSnooze
|
||||||
|
public static let logoUnlimited = ImageResource.upsellLogoUnlimited
|
||||||
|
|
||||||
public enum BlackFriday {
|
public enum BlackFriday {
|
||||||
public static let background = ImageResource.upsellBlackFridayBackground
|
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 availablePlans = try await fetchAvailablePlans()
|
||||||
|
|
||||||
let model = try upsellScreenFactory.upsellScreenModel(
|
let model = try upsellScreenFactory.upsellScreenModel(
|
||||||
showingPlan: configuration.regularPlan,
|
showingPlan: upsellType.planVariant,
|
||||||
basedOn: availablePlans,
|
basedOn: availablePlans,
|
||||||
entryPoint: entryPoint,
|
entryPoint: entryPoint,
|
||||||
upsellType: upsellType
|
upsellType: upsellType
|
||||||
|
|||||||
@@ -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")
|
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 {
|
static func numberOfEmailAddresses(_ amount: UInt) -> LocalizedStringResource {
|
||||||
.init("\(amount) email addresses", bundle: .module, comment: "Number of email addresses available in a given plan")
|
.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"
|
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 {
|
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" : {
|
"Auto-renews at the same price and terms unless canceled" : {
|
||||||
"comment" : "Notice at the bottom",
|
"comment" : "Notice at the bottom",
|
||||||
"localizations" : {
|
"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 %@." : {
|
"Deletes spam and trash after 30 days. Get this and more with %@." : {
|
||||||
"comment" : "Subtitle of the upsell page",
|
"comment" : "Subtitle of the upsell page",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -3169,6 +3175,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Folders and labels" : {
|
||||||
|
"comment" : "Description of a feature of a paid subscription"
|
||||||
|
},
|
||||||
"Free" : {
|
"Free" : {
|
||||||
"comment" : "Name of the free plan",
|
"comment" : "Name of the free plan",
|
||||||
"localizations" : {
|
"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 %@." : {
|
"Make your mobile signature your own. Enjoy this and more with %@." : {
|
||||||
"comment" : "Subtitle of the upsell page",
|
"comment" : "Subtitle of the upsell page",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@@ -5037,6 +5049,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Premium VPN, password manager and cloud storage" : {
|
||||||
|
"comment" : "Description of a feature of a paid subscription"
|
||||||
|
},
|
||||||
"Priority customer support" : {
|
"Priority customer support" : {
|
||||||
"comment" : "Description of a feature of a paid subscription",
|
"comment" : "Description of a feature of a paid subscription",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|||||||
@@ -64,7 +64,12 @@ final class TelemetryReporter: TelemetryReporting {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func generalDimensions() -> GeneralDimensions {
|
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 {
|
private func planSpecificDimensions(storeKitProductID: String) -> PlanSpecificDimensions {
|
||||||
|
|||||||