mirror of
https://github.com/ProtonMail/ios-mail.git
synced 2026-05-15 09:50:39 +00:00
Format files before build
This commit is contained in:
@@ -54,7 +54,8 @@ final class SenderImageAPIDataSource: Sendable, SenderImageDataSource {
|
||||
guard let userSession = dependencies.appContext.sessionState.userSession else {
|
||||
return nil
|
||||
}
|
||||
let senderImageResult = await userSession
|
||||
let senderImageResult =
|
||||
await userSession
|
||||
.imageForSender(
|
||||
address: params.address,
|
||||
bimiSelector: params.bimiSelector,
|
||||
|
||||
@@ -42,11 +42,11 @@ actor DeviceTokenRegistrar {
|
||||
private func prepareDeviceRegistrationRequest(deviceToken: String) -> RegisteredDevice {
|
||||
let environment: DeviceEnvironment
|
||||
|
||||
#if DEBUG
|
||||
environment = .appleDev
|
||||
#else
|
||||
environment = .appleProd
|
||||
#endif
|
||||
#if DEBUG
|
||||
environment = .appleDev
|
||||
#else
|
||||
environment = .appleProd
|
||||
#endif
|
||||
|
||||
return RegisteredDevice(
|
||||
deviceToken: deviceToken,
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ extension AllListActions {
|
||||
hiddenListActions: [
|
||||
.notSpam(.testInbox),
|
||||
.permanentDelete,
|
||||
.moveToSystemFolder(.init(localId: .init(value: 7), name: .archive))
|
||||
.moveToSystemFolder(.init(localId: .init(value: 7), name: .archive)),
|
||||
],
|
||||
visibleListActions: [.markRead, .star, .moveTo, .labelAs, .more]
|
||||
)
|
||||
|
||||
@@ -23,17 +23,18 @@ enum MoveToSheetPreviewProvider {
|
||||
static var availableMoveToActions: AvailableMoveToActions {
|
||||
.init(
|
||||
message: { _, _ in
|
||||
.ok([
|
||||
.systemFolder(.init(localId: .init(value: 1), name: .inbox)),
|
||||
.systemFolder(.init(localId: .init(value: 2), name: .archive)),
|
||||
.customFolder(customFoldersTree),
|
||||
.customFolder(.init(
|
||||
.ok([
|
||||
.systemFolder(.init(localId: .init(value: 1), name: .inbox)),
|
||||
.systemFolder(.init(localId: .init(value: 2), name: .archive)),
|
||||
.customFolder(customFoldersTree),
|
||||
.customFolder(
|
||||
.init(
|
||||
localId: .init(value: 6),
|
||||
name: "4",
|
||||
color: .init(value: "#9E221A"),
|
||||
children: []
|
||||
))
|
||||
])
|
||||
)),
|
||||
])
|
||||
},
|
||||
conversation: { _, _ in .ok([]) }
|
||||
)
|
||||
|
||||
+1
-1
@@ -44,7 +44,7 @@ extension Array where Element == AttachmentDisplayModel {
|
||||
mimeType: .init(mime: "doc", category: .pages),
|
||||
name: "Long long long long long long long long long long name",
|
||||
size: 120000
|
||||
)
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
+16
-15
@@ -23,9 +23,7 @@ extension MailboxItemCellUIModel {
|
||||
static let proton2 = makeModel(subject: "sales up to 50%", isFromProton: true)
|
||||
|
||||
static func testData() -> [MailboxItemCellUIModel] {
|
||||
[proton1] +
|
||||
MailboxItemCellUIModel.emailSubjects.map { makeModel(subject: $0) } +
|
||||
[proton2]
|
||||
[proton1] + MailboxItemCellUIModel.emailSubjects.map { makeModel(subject: $0) } + [proton2]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,11 +40,13 @@ private extension MailboxItemCellUIModel {
|
||||
enum Attachment {
|
||||
static func randomAttachment() -> [AttachmentCapsuleUIModel] {
|
||||
if [false, false, Bool.random()].randomElement()! {
|
||||
[[
|
||||
AttachmentCapsuleUIModel(id: .init(value: 1), icon: DS.Icon.icFileTypeIconPdf, name: "#34JE3KLP.pdf"),
|
||||
AttachmentCapsuleUIModel(id: .init(value: 2), icon: DS.Icon.icFileTypeIconWord, name: "meeting_minutes.doc"),
|
||||
AttachmentCapsuleUIModel(id: .init(value: 1), icon: DS.Icon.icFileTypeIconExcel, name: "ARR_Q2.xls"),
|
||||
].randomElement()!]
|
||||
[
|
||||
[
|
||||
AttachmentCapsuleUIModel(id: .init(value: 1), icon: DS.Icon.icFileTypeIconPdf, name: "#34JE3KLP.pdf"),
|
||||
AttachmentCapsuleUIModel(id: .init(value: 2), icon: DS.Icon.icFileTypeIconWord, name: "meeting_minutes.doc"),
|
||||
AttachmentCapsuleUIModel(id: .init(value: 1), icon: DS.Icon.icFileTypeIconExcel, name: "ARR_Q2.xls"),
|
||||
].randomElement()!
|
||||
]
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
@@ -69,16 +69,17 @@ private extension MailboxItemCellUIModel {
|
||||
|
||||
static func randomLabels() -> [LabelUIModel] {
|
||||
Bool.random()
|
||||
? [[work, readLater].randomElement()!] + LabelUIModel.random(num: [0, 1, 2].randomElement()!)
|
||||
: []
|
||||
? [[work, readLater].randomElement()!] + LabelUIModel.random(num: [0, 1, 2].randomElement()!)
|
||||
: []
|
||||
}
|
||||
}
|
||||
|
||||
static func makeModel(subject: String, isFromProton: Bool = false) -> MailboxItemCellUIModel {
|
||||
let avatar = isFromProton
|
||||
? .init(info: .init(initials: "P", color: .purple), type: .sender(params: .init()))
|
||||
: Avatar.randomAvatar()
|
||||
|
||||
let avatar =
|
||||
isFromProton
|
||||
? .init(info: .init(initials: "P", color: .purple), type: .sender(params: .init()))
|
||||
: Avatar.randomAvatar()
|
||||
|
||||
return MailboxItemCellUIModel(
|
||||
id: .random(),
|
||||
conversationID: .random(),
|
||||
@@ -152,7 +153,7 @@ private extension MailboxItemCellUIModel {
|
||||
"What Time Should I Pick You Up on Saturday?",
|
||||
"Looking Forward to Seeing You This Weekend",
|
||||
"Thanks for Being a Loyal Customer – Enjoy 10% Off",
|
||||
"Only a Few Hours Left – Final Sale Ends Soon!"
|
||||
"Only a Few Hours Left – Final Sale Ends Soon!",
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
@@ -53,12 +53,14 @@ struct SendingTag: View {
|
||||
}
|
||||
|
||||
#Preview {
|
||||
ZStack(alignment: .center, content: {
|
||||
VStack {
|
||||
SendingTag(variant: .sending)
|
||||
SendingTag(variant: .failure)
|
||||
}
|
||||
})
|
||||
ZStack(
|
||||
alignment: .center,
|
||||
content: {
|
||||
VStack {
|
||||
SendingTag(variant: .sending)
|
||||
SendingTag(variant: .failure)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private extension SendingTag.Variant {
|
||||
|
||||
@@ -44,9 +44,10 @@ struct AttachmentsView: View {
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
let spaceForCapsules = geometry.size.width
|
||||
- (maxNumberOfCapsules*Layout.spacingBetweenCapsules) - Layout.extraAttachmentsViewWidth
|
||||
let capsuleMaxWidth = uiModel.count == 1 ? spaceForCapsules : spaceForCapsules/CGFloat(maxNumberOfCapsules)
|
||||
let spaceForCapsules =
|
||||
geometry.size.width
|
||||
- (maxNumberOfCapsules * Layout.spacingBetweenCapsules) - Layout.extraAttachmentsViewWidth
|
||||
let capsuleMaxWidth = uiModel.count == 1 ? spaceForCapsules : spaceForCapsules / CGFloat(maxNumberOfCapsules)
|
||||
|
||||
/**
|
||||
SwiftUI does not make it easy to calculate dynamically to fit the maximum number of capsules. After trying
|
||||
@@ -189,18 +190,18 @@ fileprivate enum Layout {
|
||||
.init(id: .init(value: 5), icon: DS.Icon.icFileTypeIconCode, name: "5.bash"),
|
||||
.init(id: .init(value: 6), icon: DS.Icon.icFileTypeIconWord, name: "6.pdf"),
|
||||
.init(id: .init(value: 7), icon: DS.Icon.icFileTypeIconCode, name: "7.png"),
|
||||
.init(id: .init(value: 8), icon: DS.Icon.icFileTypeIconWord, name: "8.xls")
|
||||
.init(id: .init(value: 8), icon: DS.Icon.icFileTypeIconWord, name: "8.xls"),
|
||||
]
|
||||
)
|
||||
.border(.red)
|
||||
|
||||
AttachmentsView(
|
||||
uiModel:[
|
||||
uiModel: [
|
||||
.init(id: .init(value: 1), icon: DS.Icon.icFileTypeIconPdf, name: "super_long_title_that_goes_beyond_half.pdf"),
|
||||
.init(id: .init(value: 2), icon: DS.Icon.icFileTypeIconImage, name: "quite.png"),
|
||||
.init(id: .init(value: 3), icon: DS.Icon.icFileTypeIconExcel, name: "numebrs.xls"),
|
||||
.init(id: .init(value: 4), icon: DS.Icon.icFileTypeIconWord, name: "words.doc"),
|
||||
.init(id: .init(value: 5), icon: DS.Icon.icFileTypeIconCode, name: "scripts.bash")
|
||||
.init(id: .init(value: 5), icon: DS.Icon.icFileTypeIconCode, name: "scripts.bash"),
|
||||
]
|
||||
)
|
||||
.frame(width: 300)
|
||||
|
||||
@@ -58,23 +58,23 @@ struct AvatarCheckboxView: View {
|
||||
AvatarCheckboxView(
|
||||
isSelected: true,
|
||||
avatar: .init(info: .init(initials: "Mb", color: .cyan), type: .sender(params: .init()))
|
||||
) { _ in}
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
) { _ in }
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
|
||||
AvatarCheckboxView(
|
||||
isSelected: false,
|
||||
avatar: .init(info: .init(initials: "Mb", color: .cyan), type: .sender(params: .init()))
|
||||
) { _ in}
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
) { _ in }
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
|
||||
AvatarCheckboxView(
|
||||
isSelected: false,
|
||||
avatar: .init(info: .init(initials: "Mb", color: .cyan), type: .sender(params: .init()))
|
||||
) { _ in}
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
) { _ in }
|
||||
.square(size: 40)
|
||||
.clipped()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ import proton_app_uniffi
|
||||
|
||||
struct AllMessagesDeleter {
|
||||
private let deleteAllMessages: @Sendable (_ labelID: ID) async -> VoidActionResult
|
||||
|
||||
|
||||
init(mailUserSession: MailUserSession, wrapper: RustEmptyFolderBannerWrapper) {
|
||||
self.deleteAllMessages = { labelID in await wrapper.deleteAllMessages(mailUserSession, labelID) }
|
||||
}
|
||||
|
||||
|
||||
@discardableResult
|
||||
func deleteAll(labelID: ID) async -> VoidActionResult {
|
||||
await deleteAllMessages(labelID)
|
||||
|
||||
@@ -23,7 +23,7 @@ struct EmptyFolderBanner {
|
||||
case upgradePlan
|
||||
case emptyLocation
|
||||
}
|
||||
|
||||
|
||||
struct FolderDetails {
|
||||
let labelID: ID
|
||||
let type: SpamOrTrash
|
||||
|
||||
@@ -35,9 +35,11 @@ struct ListScrollOffsetTrackerView: View {
|
||||
.listRowInsets(EdgeInsets())
|
||||
.listRowBackground(Color.clear)
|
||||
.frame(maxHeight: 1)
|
||||
.readLayoutData(coordinateSpace: .global, onChange: { data in
|
||||
let realScrollOffset = data.frameInCoordinateSpace.minY - listTopOffset
|
||||
onScrollEvent(.onChangeOffset(value: realScrollOffset))
|
||||
})
|
||||
.readLayoutData(
|
||||
coordinateSpace: .global,
|
||||
onChange: { data in
|
||||
let realScrollOffset = data.frameInCoordinateSpace.minY - listTopOffset
|
||||
onScrollEvent(.onChangeOffset(value: realScrollOffset))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ enum OneLineLabelsListViewPreviewDataProvider {
|
||||
["Private"],
|
||||
["😈"],
|
||||
["Long long long long long long long long long long long long long long"],
|
||||
["Aaaaaaaa", "Long long label long long long long long", "aaaaaaaaaaaaa"]
|
||||
["Aaaaaaaa", "Long long label long long long long long", "aaaaaaaaaaaaa"],
|
||||
].map { $0.map(LabelUIModel.testData) }
|
||||
}
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ final class ProtonRefreshControl: UIRefreshControl, ObservableObject {
|
||||
spinnerAnimation.centerXAnchor.constraint(equalTo: centerXAnchor),
|
||||
spinnerAnimation.centerYAnchor.constraint(equalTo: centerYAnchor),
|
||||
spinnerAnimation.heightAnchor.constraint(equalToConstant: spinnerSize),
|
||||
spinnerAnimation.widthAnchor.constraint(equalTo: spinnerAnimation.heightAnchor)
|
||||
spinnerAnimation.widthAnchor.constraint(equalTo: spinnerAnimation.heightAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -18,9 +18,10 @@
|
||||
import BackgroundTasks
|
||||
|
||||
struct BackgroundTaskRegistration {
|
||||
let registerWithIdentifier: (
|
||||
_ identifier: String,
|
||||
_ queue: DispatchQueue?,
|
||||
_ handler: @escaping (BackgroundTask) -> Void
|
||||
) -> Bool
|
||||
let registerWithIdentifier:
|
||||
(
|
||||
_ identifier: String,
|
||||
_ queue: DispatchQueue?,
|
||||
_ handler: @escaping (BackgroundTask) -> Void
|
||||
) -> Bool
|
||||
}
|
||||
|
||||
+4
-2
@@ -75,7 +75,8 @@ class RecurringBackgroundTaskScheduler: @unchecked Sendable {
|
||||
|
||||
func submit() async {
|
||||
let allTaskRequests = await backgroundTaskScheduler.pendingTaskRequests()
|
||||
let isTaskSchedulled = allTaskRequests
|
||||
let isTaskSchedulled =
|
||||
allTaskRequests
|
||||
.contains(where: { request in request.identifier == Self.identifier })
|
||||
guard !isTaskSchedulled else {
|
||||
return
|
||||
@@ -137,7 +138,8 @@ class RecurringBackgroundTaskScheduler: @unchecked Sendable {
|
||||
}
|
||||
|
||||
private func checkForSessionSetUpToComplete(completion: @Sendable @escaping () -> Void) {
|
||||
sessionSetUpCheckCancellable = Publishers
|
||||
sessionSetUpCheckCancellable =
|
||||
Publishers
|
||||
.CombineLatest(sessionStatePublisher, timerFactory(0.5))
|
||||
.map { sessionState, _ in sessionState }
|
||||
.prefix(untilOutputFrom: backgroundTaskExpired)
|
||||
|
||||
@@ -22,8 +22,8 @@ import Foundation
|
||||
func forceCast<Value, ExpectedType>(_ value: Value, _ type: ExpectedType.Type) -> ExpectedType {
|
||||
guard let castedValue = value as? ExpectedType else {
|
||||
let message = """
|
||||
Could not cast value: <\(value)> of type: <\(Swift.type(of: value))> to expected type: <\(ExpectedType.self)>.
|
||||
"""
|
||||
Could not cast value: <\(value)> of type: <\(Swift.type(of: value))> to expected type: <\(ExpectedType.self)>.
|
||||
"""
|
||||
AppLogger.log(message: message)
|
||||
fatalError(message)
|
||||
}
|
||||
|
||||
@@ -33,11 +33,9 @@ extension Date {
|
||||
func mailboxFormat(calendar: Calendar = .current) -> String {
|
||||
if calendar.isDateInToday(self) {
|
||||
return formatted(.dateTime.hour().minute())
|
||||
}
|
||||
else if calendar.isDate(self, equalTo: .now, toGranularity: .year) {
|
||||
} else if calendar.isDate(self, equalTo: .now, toGranularity: .year) {
|
||||
return formatted(.dateTime.month().day())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return formatted(date: .abbreviated, time: .omitted)
|
||||
}
|
||||
}
|
||||
@@ -68,11 +66,9 @@ extension Date {
|
||||
func mailboxVoiceOverSupport(calendar: Calendar = .current) -> String {
|
||||
if calendar.isDateInToday(self) {
|
||||
return formatted(.dateTime.hour().minute())
|
||||
}
|
||||
else if calendar.isDate(self, equalTo: .now, toGranularity: .year) {
|
||||
} else if calendar.isDate(self, equalTo: .now, toGranularity: .year) {
|
||||
return formatted(.dateTime.month(.wide).day())
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return formatted(date: .long, time: .omitted)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,9 +32,10 @@ extension Date {
|
||||
isAboutToExpire = hours < 1 && minute < 1
|
||||
}
|
||||
|
||||
let text = isAboutToExpire
|
||||
? L10n.Mailbox.Item.expiresInLessThanOneMinute
|
||||
: L10n.Mailbox.Item.expiresIn(value: self.localisedRemainingTimeFromNow())
|
||||
let text =
|
||||
isAboutToExpire
|
||||
? L10n.Mailbox.Item.expiresInLessThanOneMinute
|
||||
: L10n.Mailbox.Item.expiresIn(value: self.localisedRemainingTimeFromNow())
|
||||
return ExpirationDateUIModel(text: text, color: color)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ struct CustomizeToolbarsScreenSnapshotTests {
|
||||
state: .init(
|
||||
toolbars: [
|
||||
.list(.init(selected: [.move, .archive, .label, .toggleRead], unselected: [])),
|
||||
.message(.init(selected: [.reply, .move, .forward, .toggleRead], unselected: []))
|
||||
.message(.init(selected: [.reply, .move, .forward, .toggleRead], unselected: [])),
|
||||
],
|
||||
editToolbar: nil
|
||||
),
|
||||
|
||||
@@ -35,7 +35,7 @@ extension LegacyKeychain {
|
||||
func set(privateKey privateKeyData: Data, forLabel label: PrivateKeyLabel) throws {
|
||||
let attributes: [CFString: Any] = [
|
||||
kSecAttrKeyClass: kSecAttrKeyClassPrivate,
|
||||
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom
|
||||
kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
|
||||
]
|
||||
|
||||
var error: Unmanaged<CFError>?
|
||||
@@ -51,7 +51,7 @@ extension LegacyKeychain {
|
||||
kSecAttrLabel: label.rawValue,
|
||||
kSecAttrApplicationTag: service,
|
||||
kSecAttrAccessGroup: accessGroup,
|
||||
kSecValueRef: secKey!
|
||||
kSecValueRef: secKey!,
|
||||
]
|
||||
|
||||
let status = SecItemAdd(query as CFDictionary, nil)
|
||||
@@ -65,7 +65,7 @@ extension LegacyKeychain {
|
||||
let query: [CFString: Any] = [
|
||||
kSecClass: kSecClassKey,
|
||||
kSecAttrApplicationTag: service,
|
||||
kSecAttrAccessGroup: accessGroup
|
||||
kSecAttrAccessGroup: accessGroup,
|
||||
]
|
||||
|
||||
let status = SecItemDelete(query as CFDictionary)
|
||||
|
||||
@@ -62,7 +62,8 @@ private extension MainKeyUnlockerTests {
|
||||
}
|
||||
|
||||
var biometricsProtectedMainKey: Data {
|
||||
.init(base64Encoded: "BMXkPgRzsc3e8xTY+Pnndl2HjqyZJ6rN5BF2T8t973NyeSoVs1A3CQGpwiprUAQHopyKa+ovFPzPgk5gFmZsbVIWK2qxX242iy0FOBtGWaEDjv06kQkFP34IFoCpFpfx9YhBb29+KLJd2JHebSpctqY=").unsafelyUnwrapped
|
||||
.init(base64Encoded: "BMXkPgRzsc3e8xTY+Pnndl2HjqyZJ6rN5BF2T8t973NyeSoVs1A3CQGpwiprUAQHopyKa+ovFPzPgk5gFmZsbVIWK2qxX242iy0FOBtGWaEDjv06kQkFP34IFoCpFpfx9YhBb29+KLJd2JHebSpctqY=")
|
||||
.unsafelyUnwrapped
|
||||
}
|
||||
|
||||
var secureEnclavePrivateKey: Data {
|
||||
@@ -70,7 +71,10 @@ private extension MainKeyUnlockerTests {
|
||||
}
|
||||
|
||||
var pinProtectedMainKey: Data {
|
||||
.init(base64Encoded: "dAcGOBeHqCMJvQPyOOy303bveHdY+QmCt8RpD6xX8u6+7PLF3pnUXhn91fIb2UND5P7Se8wKkKboY9a9ayFOJMm9uviXe6jCnT9C9Mh8rT3Bn04ctKPIg1YwZXCQwz80kQ/y/tW8wWACS4xRJ70v2MG5nh9jCGsi2nZ3PfFuX4545dfK0H6K0IpdwYYaZqWT6WJrGr6x+QGkJZZc6qfzLvZ7O7lmenuzc2u/fS7+fRouUROQW/2O7bo=").unsafelyUnwrapped
|
||||
.init(
|
||||
base64Encoded:
|
||||
"dAcGOBeHqCMJvQPyOOy303bveHdY+QmCt8RpD6xX8u6+7PLF3pnUXhn91fIb2UND5P7Se8wKkKboY9a9ayFOJMm9uviXe6jCnT9C9Mh8rT3Bn04ctKPIg1YwZXCQwz80kQ/y/tW8wWACS4xRJ70v2MG5nh9jCGsi2nZ3PfFuX4545dfK0H6K0IpdwYYaZqWT6WJrGr6x+QGkJZZc6qfzLvZ7O7lmenuzc2u/fS7+fRouUROQW/2O7bo="
|
||||
).unsafelyUnwrapped
|
||||
}
|
||||
|
||||
var pinProtectionSalt: Data {
|
||||
|
||||
+1
-1
@@ -86,7 +86,7 @@ final class NotificationAuthorizationStoreTests {
|
||||
|
||||
userDefaults[.notificationAuthorizationRequestDates] = [
|
||||
Calendar.autoupdatingCurrent.date(byAdding: .day, value: -20, to: .now)!,
|
||||
Calendar.autoupdatingCurrent.date(byAdding: .day, value: -10, to: .now)!
|
||||
Calendar.autoupdatingCurrent.date(byAdding: .day, value: -10, to: .now)!,
|
||||
]
|
||||
|
||||
#expect(await sut.shouldRequestAuthorization(trigger: .messageSent) == false)
|
||||
|
||||
+18
-14
@@ -114,17 +114,19 @@ class AppSettingsStateStoreTests {
|
||||
|
||||
await changeAlternativeRoutingValue(false)
|
||||
|
||||
#expect(appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useAlternativeRouting: false)
|
||||
])
|
||||
#expect(
|
||||
appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useAlternativeRouting: false)
|
||||
])
|
||||
#expect(sut.state.storedAppSettings.useAlternativeRouting == false)
|
||||
|
||||
await changeAlternativeRoutingValue(true)
|
||||
|
||||
#expect(appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useAlternativeRouting: false),
|
||||
.diff(useAlternativeRouting: true),
|
||||
])
|
||||
#expect(
|
||||
appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useAlternativeRouting: false),
|
||||
.diff(useAlternativeRouting: true),
|
||||
])
|
||||
#expect(sut.state.storedAppSettings.useAlternativeRouting == true)
|
||||
}
|
||||
|
||||
@@ -134,17 +136,19 @@ class AppSettingsStateStoreTests {
|
||||
|
||||
await changeCombinedContactsValue(true)
|
||||
|
||||
#expect(appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useCombineContacts: true)
|
||||
])
|
||||
#expect(
|
||||
appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useCombineContacts: true)
|
||||
])
|
||||
#expect(sut.state.storedAppSettings.useCombineContacts == true)
|
||||
|
||||
await changeCombinedContactsValue(false)
|
||||
|
||||
#expect(appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useCombineContacts: true),
|
||||
.diff(useCombineContacts: false),
|
||||
])
|
||||
#expect(
|
||||
appSettingsRepositorySpy.changedAppSettingsWithDiff == [
|
||||
.diff(useCombineContacts: true),
|
||||
.diff(useCombineContacts: false),
|
||||
])
|
||||
#expect(sut.state.storedAppSettings.useCombineContacts == false)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -140,7 +140,7 @@ final class ConversationDetailBottomSheetComposeTests: PMUIMockedNetworkTestCase
|
||||
$0.hasComposeButtons()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func withActionBottomSheetDisplayed(destination: UITestDestination = .inbox, interaction: (ActionBottomSheetRobot) -> Void) {
|
||||
navigator.navigateTo(destination)
|
||||
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@ final class ConversationDetailHeaderMultipleFieldsTests: PMUIMockedNetworkTestCa
|
||||
timestamp: 1718976443,
|
||||
toRecipients: [
|
||||
UITestHeaderRecipientEntry(index: 0, name: "Test Free Account", address: "notsofree@proton.black"),
|
||||
UITestHeaderRecipientEntry(index: 1, name: "notsofree+1@proton.black", address: "notsofree+1@proton.black")
|
||||
UITestHeaderRecipientEntry(index: 1, name: "notsofree+1@proton.black", address: "notsofree+1@proton.black"),
|
||||
],
|
||||
ccRecipients: [
|
||||
UITestHeaderRecipientEntry(index: 0, name: "free@proton.black", address: "free@proton.black")
|
||||
|
||||
@@ -49,7 +49,7 @@ final class ConversationDetailMessageItemsTests: PMUIMockedNetworkTestCase {
|
||||
let expectedCollapsedEntries = [
|
||||
UITestConversationCollapsedItemEntry(index: 0, senderName: "notsofree@proton.black", date: "Jun 17", preview: "to youngbee@proton.black"),
|
||||
UITestConversationCollapsedItemEntry(index: 1, senderName: "notsofree@proton.black", date: "Jun 18", preview: "to youngbee@proton.black"),
|
||||
UITestConversationCollapsedItemEntry(index: 2, senderName: "Young Bee", date: "Jun 18", preview: "to notsofree@proton.black")
|
||||
UITestConversationCollapsedItemEntry(index: 2, senderName: "Young Bee", date: "Jun 18", preview: "to notsofree@proton.black"),
|
||||
]
|
||||
|
||||
navigator.navigateTo(UITestDestination.inbox)
|
||||
|
||||
@@ -220,7 +220,7 @@ final class MailboxAttachmentPreviewsTests: PMUIMockedNetworkTestCase {
|
||||
|
||||
let capsules = [
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 0, attachmentName: "fifth.png"),
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "first.png")
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "first.png"),
|
||||
]
|
||||
let attachmentPreviews = UITestAttachmentPreviewItemEntry(items: capsules, extraItemsCount: 4)
|
||||
|
||||
@@ -321,7 +321,7 @@ final class MailboxAttachmentPreviewsTests: PMUIMockedNetworkTestCase {
|
||||
|
||||
let capsules = [
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 0, attachmentName: "zipfile.zip"),
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "fifth.png")
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "fifth.png"),
|
||||
]
|
||||
let attachmentPreviews = UITestAttachmentPreviewItemEntry(items: capsules, extraItemsCount: 5)
|
||||
|
||||
@@ -345,7 +345,7 @@ final class MailboxAttachmentPreviewsTests: PMUIMockedNetworkTestCase {
|
||||
|
||||
let capsules = [
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 0, attachmentName: "zip_conv.zip"),
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "zip_rep.zip")
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "zip_rep.zip"),
|
||||
]
|
||||
let attachmentPreviews = UITestAttachmentPreviewItemEntry(items: capsules, extraItemsCount: 7)
|
||||
|
||||
@@ -370,7 +370,7 @@ final class MailboxAttachmentPreviewsTests: PMUIMockedNetworkTestCase {
|
||||
|
||||
let capsules = [
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 0, attachmentName: "zip_conv.zip"),
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "zip_rep.zip")
|
||||
UITestAttachmentPreviewCapsuleItemEntry(index: 1, attachmentName: "zip_rep.zip"),
|
||||
]
|
||||
let attachmentPreviews = UITestAttachmentPreviewItemEntry(items: capsules, extraItemsCount: 1)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ struct UITestBottomSheetDefaultEntries {
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 2, text: "Print"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 3, text: "View headers"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 4, text: "View HTML"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing")
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing"),
|
||||
]
|
||||
|
||||
static let defaultTrashList = [
|
||||
@@ -49,7 +49,7 @@ struct UITestBottomSheetDefaultEntries {
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 2, text: "Print"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 3, text: "View headers"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 4, text: "View HTML"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing")
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing"),
|
||||
]
|
||||
|
||||
static let defaultSpamList = [
|
||||
@@ -65,7 +65,7 @@ struct UITestBottomSheetDefaultEntries {
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 2, text: "Print"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 3, text: "View headers"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 4, text: "View HTML"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing")
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing"),
|
||||
]
|
||||
|
||||
static let defaultArchiveList = [
|
||||
@@ -81,7 +81,7 @@ struct UITestBottomSheetDefaultEntries {
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 2, text: "Print"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 3, text: "View headers"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 4, text: "View HTML"),
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing")
|
||||
UITestBottomSheetDynamicEntry(section: 2, index: 5, text: "Report phishing"),
|
||||
]
|
||||
|
||||
static let defaultSenderActions = [
|
||||
@@ -96,7 +96,7 @@ struct UITestBottomSheetDefaultEntries {
|
||||
UITestBottomSheetDynamicEntry(section: 0, index: 0, text: "Message"),
|
||||
UITestBottomSheetDynamicEntry(section: 0, index: 1, text: "Add to contacts"),
|
||||
UITestBottomSheetDynamicEntry(section: 1, index: 0, text: "Copy address"),
|
||||
UITestBottomSheetDynamicEntry(section: 1, index: 1, text: "Copy name")
|
||||
UITestBottomSheetDynamicEntry(section: 1, index: 1, text: "Copy name"),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+5
-2
@@ -25,8 +25,11 @@ struct UITestConversationExpandedHeaderEntry {
|
||||
let toRecipients: [UITestHeaderRecipientEntry]
|
||||
let ccRecipients: [UITestHeaderRecipientEntry]?
|
||||
let bccRecipients: [UITestHeaderRecipientEntry]?
|
||||
|
||||
init(index: Int, senderName: String, senderAddress: String, timestamp: UInt64, toRecipients: [UITestHeaderRecipientEntry], ccRecipients: [UITestHeaderRecipientEntry]? = nil, bccRecipients: [UITestHeaderRecipientEntry]? = nil) {
|
||||
|
||||
init(
|
||||
index: Int, senderName: String, senderAddress: String, timestamp: UInt64, toRecipients: [UITestHeaderRecipientEntry], ccRecipients: [UITestHeaderRecipientEntry]? = nil,
|
||||
bccRecipients: [UITestHeaderRecipientEntry]? = nil
|
||||
) {
|
||||
self.index = index
|
||||
self.senderName = senderName
|
||||
self.senderAddress = senderAddress
|
||||
|
||||
@@ -25,7 +25,7 @@ struct UITestMailboxListItemEntry {
|
||||
let date: String
|
||||
let count: Int?
|
||||
let attachmentPreviews: UITestAttachmentPreviewItemEntry?
|
||||
|
||||
|
||||
init(index: Int, avatar: UITestAvatarItemEntry, sender: String, subject: String, date: String, count: Int? = nil, attachmentPreviews: UITestAttachmentPreviewItemEntry? = nil) {
|
||||
self.index = index
|
||||
self.avatar = avatar
|
||||
|
||||
@@ -38,12 +38,12 @@ extension Robot {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
|
||||
func waitMessageBySubject(subject: String) {
|
||||
let subjectText = application.staticTexts[subject].firstMatch
|
||||
subjectText.waitUntilShown()
|
||||
}
|
||||
|
||||
|
||||
func clickMessageBySubject(subject: String) {
|
||||
let subjectText = application.staticTexts[subject].firstMatch
|
||||
subjectText.tap()
|
||||
|
||||
@@ -25,11 +25,11 @@ public class DurationMeasurement: Measurement {
|
||||
return stopTime - startTime
|
||||
}
|
||||
|
||||
public init(startTime: TimeInterval = Date().timeIntervalSince1970 , stopTime: TimeInterval = Date().timeIntervalSince1970) {
|
||||
public init(startTime: TimeInterval = Date().timeIntervalSince1970, stopTime: TimeInterval = Date().timeIntervalSince1970) {
|
||||
self.startTime = startTime
|
||||
self.stopTime = stopTime
|
||||
}
|
||||
|
||||
|
||||
public func onStartMeasurement(measurementProfile: MeasurementProfile) {
|
||||
startTime = Date().timeIntervalSince1970
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ enum LokiPushError: Error {
|
||||
@available(macOS 12.0, *)
|
||||
class LokiClient {
|
||||
|
||||
init(){
|
||||
init() {
|
||||
self.session = URLSession(configuration: .default, delegate: CustomSessionDelegate(), delegateQueue: nil)
|
||||
}
|
||||
|
||||
@@ -89,4 +89,3 @@ class LokiClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
// 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 Foundation
|
||||
import XCTest
|
||||
|
||||
@@ -35,7 +34,9 @@ public class MeasureBlock {
|
||||
internal func startMeasurement() {
|
||||
profile.measuresList.append(self)
|
||||
if self.labels["sli"] as! String == "unknown" {
|
||||
XCTFail("MeasurementProfile: measure block for profile with workflow: \"\(profile.workflow)\" expected Service Level Indicator to be set via profile.setServiceLevelIndicator() but it wasn't. Current value is \"null\".")
|
||||
XCTFail(
|
||||
"MeasurementProfile: measure block for profile with workflow: \"\(profile.workflow)\" expected Service Level Indicator to be set via profile.setServiceLevelIndicator() but it wasn't. Current value is \"null\"."
|
||||
)
|
||||
}
|
||||
profile.measurements.forEach { $0.onStartMeasurement(measurementProfile: profile) }
|
||||
}
|
||||
@@ -47,17 +48,19 @@ public class MeasureBlock {
|
||||
}
|
||||
|
||||
public func getMeasureStream() -> [String: Any] {
|
||||
let timestamp = Int(Date().timeIntervalSince1970 * 1_000_000_000) // Nanoseconds since epoch
|
||||
let timestamp = Int(Date().timeIntervalSince1970 * 1_000_000_000) // Nanoseconds since epoch
|
||||
|
||||
let values: [[Any]] = [[
|
||||
"\(timestamp)",
|
||||
metrics.jsonString(),
|
||||
metadata
|
||||
]]
|
||||
let values: [[Any]] = [
|
||||
[
|
||||
"\(timestamp)",
|
||||
metrics.jsonString(),
|
||||
metadata,
|
||||
]
|
||||
]
|
||||
|
||||
return [
|
||||
"stream": profile.sharedLabels,
|
||||
"values": values
|
||||
"values": values,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -116,4 +119,3 @@ extension Dictionary {
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,9 @@
|
||||
// along with Proton Mail. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
import AppKit
|
||||
#else
|
||||
import UIKit
|
||||
import UIKit
|
||||
#endif
|
||||
|
||||
public class MeasurementProfile: MeasurementProtocol {
|
||||
@@ -33,15 +33,15 @@ public class MeasurementProfile: MeasurementProtocol {
|
||||
public init(workflow: String) {
|
||||
self.workflow = workflow
|
||||
|
||||
#if os(macOS)
|
||||
let platform = "macOS"
|
||||
let systemVersion = ProcessInfo.processInfo.operatingSystemVersionString
|
||||
let deviceModel = "Mac"
|
||||
#else
|
||||
let platform = "iOS"
|
||||
let systemVersion = UIDevice.current.systemVersion
|
||||
let deviceModel = UIDevice.current.model
|
||||
#endif
|
||||
#if os(macOS)
|
||||
let platform = "macOS"
|
||||
let systemVersion = ProcessInfo.processInfo.operatingSystemVersionString
|
||||
let deviceModel = "Mac"
|
||||
#else
|
||||
let platform = "iOS"
|
||||
let systemVersion = UIDevice.current.systemVersion
|
||||
let deviceModel = UIDevice.current.model
|
||||
#endif
|
||||
|
||||
self.sharedLabels = [
|
||||
"workflow": workflow,
|
||||
@@ -56,12 +56,12 @@ public class MeasurementProfile: MeasurementProtocol {
|
||||
self.sharedMetadata = [
|
||||
"app_version": MeasurementConfig.version,
|
||||
"build_commit_sha1": MeasurementConfig.buildCommitShortSha,
|
||||
"ci_job_id": MeasurementConfig.ciJobId
|
||||
"ci_job_id": MeasurementConfig.ciJobId,
|
||||
]
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func addMeasurement(_ measurement: Measurement) -> MeasurementProfile {
|
||||
public func addMeasurement(_ measurement: Measurement) -> MeasurementProfile {
|
||||
measurements.append(measurement)
|
||||
return self
|
||||
}
|
||||
@@ -78,14 +78,14 @@ public class MeasurementProfile: MeasurementProtocol {
|
||||
return components.joined(separator: "_")
|
||||
}
|
||||
|
||||
public func addMetricToMeasures(_ key: String, _ value: String) {
|
||||
public func addMetricToMeasures(_ key: String, _ value: String) {
|
||||
measuresList.forEach { measure in
|
||||
measure.addMetric(key: key, value: value)
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func addMetrics(data: [String: String]) -> MeasurementProfile {
|
||||
public func addMetrics(data: [String: String]) -> MeasurementProfile {
|
||||
measuresList.forEach { measure in
|
||||
measure.addMetrics(data)
|
||||
}
|
||||
@@ -93,7 +93,7 @@ public class MeasurementProfile: MeasurementProtocol {
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
public func setServiceLevelIndicator(_ sli: String) -> MeasurementProfile {
|
||||
public func setServiceLevelIndicator(_ sli: String) -> MeasurementProfile {
|
||||
self.serviceLevelIndicator = sli
|
||||
sharedLabels["sli"] = sli
|
||||
return self
|
||||
@@ -118,4 +118,3 @@ public class MeasurementProfile: MeasurementProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,12 +22,13 @@ func getTestConfigValue(forKey key: String) -> String {
|
||||
|
||||
for bundle in testBundles {
|
||||
if let url = bundle.url(forResource: "Loki", withExtension: "plist"),
|
||||
let data = try? Data(contentsOf: url),
|
||||
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any],
|
||||
let value = plist[key] as? String {
|
||||
let data = try? Data(contentsOf: url),
|
||||
let plist = try? PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any],
|
||||
let value = plist[key] as? String
|
||||
{
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return ProcessInfo.processInfo.environment[key] ?? "invalid"
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import Foundation
|
||||
extension Date {
|
||||
/**
|
||||
Calculates a future date by applying an initial time buffer and then rounding up to the nearest specified minute interval.
|
||||
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
```swift
|
||||
let now = Date() // Assume current time is 10:07
|
||||
let roundedDate = now.roundedUp(
|
||||
@@ -31,7 +31,7 @@ extension Date {
|
||||
)
|
||||
// roundedDate will be 10:30
|
||||
```
|
||||
|
||||
|
||||
*/
|
||||
func roundedUp(by minuteInterval: TimeInterval, withInitialBuffer bufferInMinutes: TimeInterval) -> Date {
|
||||
let futureTime = addingTimeInterval(bufferInMinutes * 60)
|
||||
|
||||
@@ -35,7 +35,6 @@ enum DiscardDraftAlertAction: AlertActionInfo, CaseIterable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ExpiringMessageUnsupportedAlertAction: AlertActionInfo, CaseIterable {
|
||||
case sendAnyway
|
||||
case addPassword
|
||||
|
||||
+1
-2
@@ -19,8 +19,7 @@ import UIKit
|
||||
|
||||
extension UIStackView {
|
||||
|
||||
func addArrangedSubviewWithInsets(_ view: UIView, insets: UIEdgeInsets)
|
||||
{
|
||||
func addArrangedSubviewWithInsets(_ view: UIView, insets: UIEdgeInsets) {
|
||||
let container = UIView()
|
||||
container.addSubview(view)
|
||||
NSLayoutConstraint.activate([
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ final class DraftActionBarViewController: UIViewController {
|
||||
}
|
||||
|
||||
stack.addArrangedSubview(passwordButton)
|
||||
// stack.addArrangedSubview(expirationButton) // TODO: Enable when SDK is ready
|
||||
// stack.addArrangedSubview(expirationButton) // TODO: Enable when SDK is ready
|
||||
stack.addArrangedSubview(spacer)
|
||||
stack.addArrangedSubview(discardButton)
|
||||
attachmentButton.addTarget(self, action: #selector(onAttachmentTap), for: .touchUpInside)
|
||||
|
||||
@@ -34,8 +34,11 @@ private struct FileImporterModifier: ViewModifier {
|
||||
|
||||
extension View {
|
||||
func fileImporter(isPresented: Binding<Bool>, onCompletion: @escaping (Result<[URL], any Error>) async -> Void) -> some View {
|
||||
modifier(FileImporterModifier(isPresented: isPresented, onCompletion: { result in
|
||||
Task { await onCompletion(result) }
|
||||
}))
|
||||
modifier(
|
||||
FileImporterModifier(
|
||||
isPresented: isPresented,
|
||||
onCompletion: { result in
|
||||
Task { await onCompletion(result) }
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ extension PhotosItemFile: Transferable {
|
||||
},
|
||||
importing: { received in
|
||||
let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
|
||||
let tempFolder = cacheDirectory
|
||||
let tempFolder =
|
||||
cacheDirectory
|
||||
.appendingPathComponent(tempDestinationFolderName, isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: true)
|
||||
try fileManager.createDirectory(at: tempFolder, withIntermediateDirectories: true)
|
||||
|
||||
+2
-2
@@ -152,7 +152,7 @@ protocol PhotosPickerItemTransferable {
|
||||
extension PhotosPickerItem: PhotosPickerItemTransferable {}
|
||||
|
||||
private extension URL {
|
||||
|
||||
|
||||
/// File of type HEIC. This type identifies pictures taken with the camera of the device
|
||||
var isHeicType: Bool {
|
||||
utType?.conforms(to: .heic) ?? false
|
||||
@@ -166,7 +166,7 @@ private extension URL {
|
||||
private var utType: UTType? {
|
||||
do {
|
||||
if let typeIdentifier = try resourceValues(forKeys: [.typeIdentifierKey]).typeIdentifier {
|
||||
return UTType(typeIdentifier)
|
||||
return UTType(typeIdentifier)
|
||||
}
|
||||
} catch {
|
||||
AppLogger.log(error: error, category: .composer)
|
||||
|
||||
+6
-5
@@ -40,10 +40,11 @@ extension View {
|
||||
additionallyObserving modalAction: Binding<ComposerViewModalState?>, // Change parameter
|
||||
content: ComposerViewModalFactory
|
||||
) -> some View {
|
||||
modifier(ComposerModalModifier(
|
||||
modalState: item,
|
||||
modalAction: modalAction,
|
||||
modalFactory: content
|
||||
))
|
||||
modifier(
|
||||
ComposerModalModifier(
|
||||
modalState: item,
|
||||
modalAction: modalAction,
|
||||
modalFactory: content
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ enum L10n {
|
||||
}
|
||||
|
||||
static let failedToDecryptExternalEncryptionPassword = LocalizedStringResource(
|
||||
"Failed to decrypt external encryption password", // FIXME: - To verify
|
||||
"Failed to decrypt external encryption password", // FIXME: - To verify
|
||||
comment: "Error in the context of scheduling a message."
|
||||
)
|
||||
|
||||
|
||||
@@ -73,7 +73,8 @@ final class FilePickerItemHandlerTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testAddSelectedFiles_whenThereIsAnErrorInTheResults_itShouldReturnError() async throws {
|
||||
let error = NSError(domain: "".notLocalized, code: -1,
|
||||
let error = NSError(
|
||||
domain: "".notLocalized, code: -1,
|
||||
userInfo: [NSLocalizedDescriptionKey: "the localised error".notLocalized]
|
||||
)
|
||||
await sut.addSelectedFiles(to: mockDraft, selectionResult: .failure(error), onErrors: mockOnErrors)
|
||||
|
||||
@@ -46,11 +46,12 @@ public struct ContactSuggestionsRepository {
|
||||
// MARK: - Private
|
||||
|
||||
private func deviceContacts() -> [DeviceContact] {
|
||||
let keys: [CNKeyDescriptor] = [
|
||||
CNContactGivenNameKey,
|
||||
CNContactFamilyNameKey,
|
||||
CNContactEmailAddressesKey
|
||||
] as [CNKeyDescriptor]
|
||||
let keys: [CNKeyDescriptor] =
|
||||
[
|
||||
CNContactGivenNameKey,
|
||||
CNContactFamilyNameKey,
|
||||
CNContactEmailAddressesKey,
|
||||
] as [CNKeyDescriptor]
|
||||
let request = CNContactFetchRequest(keysToFetch: keys)
|
||||
var contacts: [DeviceContact] = []
|
||||
|
||||
|
||||
@@ -43,10 +43,11 @@ extension RustContactsWrappers {
|
||||
}
|
||||
|
||||
public struct AllContactsProvider {
|
||||
public let contactSuggestions: (
|
||||
_ deviceContacts: [DeviceContact],
|
||||
_ userSession: MailUserSession
|
||||
) async -> ContactSuggestionsResult
|
||||
public let contactSuggestions:
|
||||
(
|
||||
_ deviceContacts: [DeviceContact],
|
||||
_ userSession: MailUserSession
|
||||
) async -> ContactSuggestionsResult
|
||||
|
||||
public init(
|
||||
contactSuggestions: @escaping ([DeviceContact], MailUserSession) async -> ContactSuggestionsResult
|
||||
@@ -64,10 +65,11 @@ public struct GroupedContactsProvider {
|
||||
}
|
||||
|
||||
public struct ContactsWatcher {
|
||||
public let watch: (
|
||||
_ session: MailUserSession,
|
||||
_ callback: ContactsLiveQueryCallback
|
||||
) async -> WatchContactListResult
|
||||
public let watch:
|
||||
(
|
||||
_ session: MailUserSession,
|
||||
_ callback: ContactsLiveQueryCallback
|
||||
) async -> WatchContactListResult
|
||||
}
|
||||
|
||||
extension GroupedContactsProvider {
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ final class ContactGroupCell: UITableViewCell {
|
||||
iconImageView.centerXAnchor.constraint(equalTo: iconBackgroundView.centerXAnchor),
|
||||
iconImageView.centerYAnchor.constraint(equalTo: iconBackgroundView.centerYAnchor),
|
||||
iconImageView.widthAnchor.constraint(equalToConstant: 20),
|
||||
iconImageView.heightAnchor.constraint(equalTo: iconImageView.widthAnchor)
|
||||
iconImageView.heightAnchor.constraint(equalTo: iconImageView.widthAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ final class ContactLabelsView: UIView {
|
||||
stackView.topAnchor.constraint(equalTo: topAnchor),
|
||||
stackView.bottomAnchor.constraint(equalTo: bottomAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ final class NoContactsPlaceholderView: UIView {
|
||||
stackView.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -40),
|
||||
stackView.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor),
|
||||
stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor)
|
||||
stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ extension URL {
|
||||
static func create(domain: String) -> URL {
|
||||
url(domain: domain, path: "/inbox#create-contact")
|
||||
}
|
||||
static func edit(domain: String, id: String) -> URL {
|
||||
static func edit(domain: String, id: String) -> URL {
|
||||
url(domain: domain, path: "/inbox#edit-contact=:\(id)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,7 @@ class CNContactStoreSpy: CNContactStoring {
|
||||
|
||||
static var stubbedAuthorizationStatus: [CNEntityType: CNAuthorizationStatus] = .default
|
||||
|
||||
private(set) var requestAccessCalls: [
|
||||
(entityType: CNEntityType, completionHandler: (Bool, (any Error)?) -> Void)
|
||||
] = []
|
||||
private(set) var requestAccessCalls: [(entityType: CNEntityType, completionHandler: (Bool, (any Error)?) -> Void)] = []
|
||||
private(set) var enumerateContactsCalls: [CNContactFetchRequest] = []
|
||||
var stubbedEnumerateContacts: [CNContact] = []
|
||||
var requestAccessCompletionBlockCalledImmediately: Bool = false
|
||||
|
||||
@@ -102,8 +102,7 @@ extension Event {
|
||||
|
||||
// Check for unhandled exceptions or for specific crash mechanisms
|
||||
return exceptions.contains(where: {
|
||||
$0.mechanism?.handled == false ||
|
||||
["signal", "mach_exception", "uncaught_exception"].contains($0.mechanism?.type)
|
||||
$0.mechanism?.handled == false || ["signal", "mach_exception", "uncaught_exception"].contains($0.mechanism?.type)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import Combine
|
||||
public typealias DispatchQueueScheduler = AnyScheduler<DispatchQueue.SchedulerTimeType>
|
||||
|
||||
public class AnyScheduler<SchedulerTimeType>: Scheduler
|
||||
where SchedulerTimeType: Strideable, SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {
|
||||
where SchedulerTimeType: Strideable, SchedulerTimeType.Stride: SchedulerTimeIntervalConvertible {
|
||||
|
||||
public typealias SchedulerOptions = Never
|
||||
|
||||
@@ -70,16 +70,18 @@ public class AnyScheduler<SchedulerTimeType>: Scheduler
|
||||
private let _now: () -> SchedulerTimeType
|
||||
private let _minimumTolerance: () -> SchedulerTimeType.Stride
|
||||
private let _schedule_action: (@escaping () -> Void) -> Void
|
||||
private let _schedule_after_tolerance_action: (
|
||||
SchedulerTimeType,
|
||||
SchedulerTimeType.Stride,
|
||||
@escaping () -> Void
|
||||
) -> Void
|
||||
private let _schedule_after_interval_tolerance_action: (
|
||||
SchedulerTimeType,
|
||||
SchedulerTimeType.Stride,
|
||||
SchedulerTimeType.Stride,
|
||||
@escaping () -> Void
|
||||
) -> Cancellable
|
||||
private let _schedule_after_tolerance_action:
|
||||
(
|
||||
SchedulerTimeType,
|
||||
SchedulerTimeType.Stride,
|
||||
@escaping () -> Void
|
||||
) -> Void
|
||||
private let _schedule_after_interval_tolerance_action:
|
||||
(
|
||||
SchedulerTimeType,
|
||||
SchedulerTimeType.Stride,
|
||||
SchedulerTimeType.Stride,
|
||||
@escaping () -> Void
|
||||
) -> Cancellable
|
||||
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ extension Bundle {
|
||||
The effectiveAppVersion property returns the public-facing version of the app,
|
||||
used for features such as human verification and bug reporting. It ensures that
|
||||
the version is never reported as lower than "7.0.1" (the official initial release version).
|
||||
|
||||
|
||||
Examples:
|
||||
- If the version is "0.2.0", effectiveAppVersion returns "7.0.1" (minimum enforced version).
|
||||
- If the version is "7.1.1" or "11.0.0", effectiveAppVersion returns the actual version ("7.1.1" or "11.0.0").
|
||||
|
||||
|
||||
Once the app's internal version naturally reaches or exceeds "7.0.1", this property
|
||||
will reflect the real app version, so we can get rid of it and start using bundleShortVersion.
|
||||
*/
|
||||
|
||||
@@ -44,15 +44,15 @@ private struct DismissKey: EnvironmentKey {
|
||||
|
||||
func callAsFunction() {
|
||||
let message = """
|
||||
This should not be used at runtime. For testing purposes, please inject
|
||||
a test double into the SwiftUI view using the `environment(_: _:)` function, specifying
|
||||
the keyPath defined in the scope of `EnvironmentValues`, for example:
|
||||
This should not be used at runtime. For testing purposes, please inject
|
||||
a test double into the SwiftUI view using the `environment(_: _:)` function, specifying
|
||||
the keyPath defined in the scope of `EnvironmentValues`, for example:
|
||||
|
||||
```
|
||||
let sut: View = ...
|
||||
sut.environment(\\.dismissable, DismissSpy())
|
||||
```
|
||||
"""
|
||||
```
|
||||
let sut: View = ...
|
||||
sut.environment(\\.dismissable, DismissSpy())
|
||||
```
|
||||
"""
|
||||
fatalError(message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import Foundation
|
||||
func forceCast<Value, ExpectedType>(_ value: Value, _ type: ExpectedType.Type) -> ExpectedType {
|
||||
guard let castedValue = value as? ExpectedType else {
|
||||
let message = """
|
||||
Could not cast value: <\(value)> of type: <\(Swift.type(of: value))> to expected type: <\(ExpectedType.self)>.
|
||||
"""
|
||||
Could not cast value: <\(value)> of type: <\(Swift.type(of: value))> to expected type: <\(ExpectedType.self)>.
|
||||
"""
|
||||
AppLogger.log(message: message)
|
||||
fatalError(message)
|
||||
}
|
||||
|
||||
@@ -34,4 +34,3 @@ extension DateFormatter {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ public struct FormSection<Content: View>: View {
|
||||
public init(
|
||||
header: LocalizedStringResource? = nil,
|
||||
footer: LocalizedStringResource? = nil,
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
@ViewBuilder content: @escaping () -> Content
|
||||
) {
|
||||
self.header = header
|
||||
self.footer = footer
|
||||
|
||||
@@ -7,7 +7,7 @@ let package = Package(
|
||||
name: "InboxDesignSystem",
|
||||
platforms: [.iOS(.v17)],
|
||||
products: [
|
||||
.library(name: "InboxDesignSystem", targets: ["InboxDesignSystem"]),
|
||||
.library(name: "InboxDesignSystem", targets: ["InboxDesignSystem"])
|
||||
],
|
||||
targets: [
|
||||
.target(name: "InboxDesignSystem", resources: [.process("Resources")])
|
||||
|
||||
-1
@@ -49,4 +49,3 @@ private extension DescriptionEntitlement {
|
||||
.init(type: .empty, text: text, iconName: iconName, hint: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,11 @@ let package = Package(
|
||||
name: "InboxKeychain",
|
||||
platforms: [.iOS(.v17)],
|
||||
products: [
|
||||
.library(name: "InboxKeychain", targets: ["InboxKeychain"]),
|
||||
.library(name: "InboxKeychain", targets: ["InboxKeychain"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../InboxCore"),
|
||||
.package(path: "../../ProtonPackages/proton_app_uniffi")
|
||||
.package(path: "../../ProtonPackages/proton_app_uniffi"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "InboxKeychain", dependencies: ["InboxCore", "proton_app_uniffi"]),
|
||||
|
||||
@@ -212,7 +212,7 @@ open class Keychain {
|
||||
kSecReturnData as String: kCFBooleanTrue,
|
||||
kSecMatchLimit as String: kSecMatchLimitOne,
|
||||
kSecAttrAccessGroup as String: self.accessGroup as AnyObject,
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
|
||||
]
|
||||
if #available(macOS 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) {
|
||||
query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
|
||||
@@ -269,7 +269,7 @@ open class Keychain {
|
||||
kSecAttrService as String: self.service as AnyObject,
|
||||
kSecAttrAccount as String: key as AnyObject,
|
||||
kSecAttrAccessGroup as String: self.accessGroup as AnyObject,
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
|
||||
]
|
||||
if #available(macOS 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) {
|
||||
query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
|
||||
@@ -303,7 +303,7 @@ open class Keychain {
|
||||
kSecAttrService as String: self.service as AnyObject,
|
||||
kSecAttrAccount as String: key as AnyObject,
|
||||
kSecAttrAccessGroup as String: self.accessGroup as AnyObject,
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
|
||||
]
|
||||
if #available(macOS 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) {
|
||||
query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
|
||||
@@ -329,7 +329,7 @@ open class Keychain {
|
||||
guard codeExisting == errSecItemNotFound else {
|
||||
var updateAttributes: [String: AnyObject] = [
|
||||
kSecAttrSynchronizable as String: NSNumber(value: false),
|
||||
kSecValueData as String: value as AnyObject
|
||||
kSecValueData as String: value as AnyObject,
|
||||
]
|
||||
self.injectAccessControlAttributes(into: &updateAttributes)
|
||||
|
||||
@@ -370,7 +370,7 @@ open class Keychain {
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: self.service as AnyObject,
|
||||
kSecAttrAccessGroup as String: self.accessGroup as AnyObject,
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny
|
||||
kSecAttrSynchronizable as String: kSecAttrSynchronizableAny,
|
||||
]
|
||||
if #available(macOS 10.15, tvOS 13.0, watchOS 6.0, macCatalyst 13.0, *) {
|
||||
query[kSecUseDataProtectionKeychain as String] = kCFBooleanTrue
|
||||
|
||||
@@ -21,7 +21,7 @@ import proton_app_uniffi
|
||||
extension RsvpEvent {
|
||||
|
||||
static func testData(
|
||||
id: String? = .none,
|
||||
id: String? = .none,
|
||||
summary: String? = .none,
|
||||
startsAt: UnixTimestamp = .zero,
|
||||
organizer: RsvpOrganizer = .init(name: .empty, email: .empty),
|
||||
|
||||
@@ -40,8 +40,8 @@ public class DispatchQueueImmediateScheduler: Scheduler {
|
||||
after date: DispatchQueue.SchedulerTimeType,
|
||||
tolerance: DispatchQueue.SchedulerTimeType.Stride,
|
||||
options: DispatchQueue.SchedulerOptions?,
|
||||
_ action: @escaping () -> Void)
|
||||
{
|
||||
_ action: @escaping () -> Void
|
||||
) {
|
||||
action()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,12 +7,12 @@ let package = Package(
|
||||
name: "TestableNotificationService",
|
||||
platforms: [.iOS(.v17)],
|
||||
products: [
|
||||
.library(name: "TestableNotificationService", targets: ["TestableNotificationService"]),
|
||||
.library(name: "TestableNotificationService", targets: ["TestableNotificationService"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "../InboxCore"),
|
||||
.package(path: "../InboxKeychain"),
|
||||
.package(path: "../../ProtonPackages/proton_app_uniffi")
|
||||
.package(path: "../../ProtonPackages/proton_app_uniffi"),
|
||||
],
|
||||
targets: [
|
||||
.target(name: "TestableNotificationService", dependencies: ["InboxCore", "InboxKeychain", "proton_app_uniffi"]),
|
||||
|
||||
@@ -25,7 +25,8 @@ enum L10n {
|
||||
static let messageSent = LocalizedStringResource("Message sent!", bundle: .module, comment: "Alert shown after the message has been sent")
|
||||
}
|
||||
|
||||
static let needToSignIn = LocalizedStringResource("You need to sign-in to Proton Mail to share content.", bundle: .module, comment: "Error message when attempting to use the Share extension without being logged in")
|
||||
static let needToSignIn = LocalizedStringResource(
|
||||
"You need to sign-in to Proton Mail to share content.", bundle: .module, comment: "Error message when attempting to use the Share extension without being logged in")
|
||||
|
||||
static let openApp = LocalizedStringResource("Open Proton Mail", bundle: .module, comment: "Button to open the main app from the Share extension")
|
||||
}
|
||||
|
||||
+16
-7
@@ -136,7 +136,22 @@ targets:
|
||||
platform: iOS
|
||||
supportedDestinations:
|
||||
- iOS
|
||||
scheme: {}
|
||||
scheme:
|
||||
preActions:
|
||||
- name: swift-format
|
||||
script: |
|
||||
if [ $ACTION == "build" ]; then
|
||||
cd "$SRCROOT"
|
||||
xcrun swift-format format -r Modules -p -i
|
||||
fi
|
||||
settingsTarget: ProtonMail
|
||||
- name: Sourcery
|
||||
script: |
|
||||
if [ $ACTION == "build" ]; then
|
||||
cd "$SRCROOT"
|
||||
find sourcery/configs -name "*.yaml" -exec /opt/homebrew/bin/mint run sourcery --config {} \;
|
||||
fi
|
||||
settingsTarget: ProtonMail
|
||||
sources:
|
||||
- path: Modules/App/Sources
|
||||
settings:
|
||||
@@ -211,12 +226,6 @@ targets:
|
||||
- package: Scrypt
|
||||
- target: ShareExtension
|
||||
- package: SwiftUIIntrospect
|
||||
preBuildScripts:
|
||||
- name: swift-format
|
||||
script: xcrun swift-format lint -r Modules -p
|
||||
basedOnDependencyAnalysis: false
|
||||
- name: Sourcery
|
||||
script: find sourcery/configs -name "*.yaml" -exec /opt/homebrew/bin/mint run sourcery --config {} \;
|
||||
|
||||
ProtonMailTest:
|
||||
type: bundle.unit-test
|
||||
|
||||
Reference in New Issue
Block a user