refactor(swift-format): Full config

This commit is contained in:
Jacek Krasiukianis
2025-12-16 09:58:19 +01:00
parent fa54a216fa
commit d56170a999
88 changed files with 505 additions and 343 deletions
+66 -2
View File
@@ -1,15 +1,79 @@
{
"fileScopedDeclarationPrivacy": {
"accessLevel": "private"
},
"indentBlankLines": false,
"indentConditionalCompilationBlocks": true,
"indentSwitchCaseLabels": false,
"indentation": {
"spaces": 4
},
"lineBreakAroundMultilineExpressionChainComponents": true,
"lineBreakBeforeControlFlowKeywords": false,
"lineBreakBeforeEachArgument": false,
"lineBreakBeforeEachGenericRequirement": false,
"lineBreakBetweenDeclarationAttributes": false,
"lineLength": 200,
"maximumBlankLines": 1,
"multiElementCollectionTrailingCommas": true,
"multilineTrailingCommaBehavior": "alwaysUsed",
"noAssignmentInExpressions": {
"allowedFunctions": [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether": false,
"reflowMultilineStringLiterals": {
"never": {}
},
"respectsExistingLineBreaks": true,
"rules": {
"AllPublicDeclarationsHaveDocumentation": false,
"AlwaysUseLiteralForEmptyCollectionInit": true,
"AlwaysUseLowerCamelCase": true,
"AmbiguousTrailingClosureOverload": true,
"AvoidRetroactiveConformances": false,
"BeginDocumentationCommentWithOneLineSummary": true,
"DoNotUseSemicolons": true,
"DontRepeatTypeInStaticProperties": true,
"FileScopedDeclarationPrivacy": true,
"FullyIndirectEnum": true,
"GroupNumericLiterals": true,
"IdentifiersMustBeASCII": true,
"NeverForceUnwrap": false,
"NeverUseForceTry": false,
"NeverUseImplicitlyUnwrappedOptionals": false,
"NoAccessLevelOnExtensionDeclaration": false,
"NoAssignmentInExpressions": true,
"NoBlockComments": false,
"NoCasesWithOnlyFallthrough": true,
"NoEmptyLinesOpeningClosingBraces": true,
"NoEmptyTrailingClosureParentheses": true,
"NoLabelsInCasePatterns": true,
"NoLeadingUnderscores": false,
"NoParensAroundConditions": true,
"NoPlaygroundLiterals": true,
"NoVoidReturnOnFunctionSignature": true,
"OmitExplicitReturns": true,
"OneCasePerLine": true,
"OneVariableDeclarationPerLine": true,
"OnlyOneTrailingClosureArgument": true,
"OrderedImports": true,
"UseTripleSlashForDocumentationComments": true
}
"ReplaceForEachWithForLoop": true,
"ReturnVoidInsteadOfEmptyTuple": true,
"TypeNamesShouldBeCapitalized": true,
"UseEarlyExits": false,
"UseExplicitNilCheckInConditions": true,
"UseLetInEveryBoundCaseVariable": true,
"UseShorthandTypeNames": true,
"UseSingleLinePropertyGetter": true,
"UseSynthesizedInitializer": true,
"UseTripleSlashForDocumentationComments": true,
"UseWhereClausesInForLoops": true,
"ValidateDocumentationComments": true
},
"spacesAroundRangeFormationOperators": false,
"spacesBeforeEndOfLineComments": 2,
"tabWidth": 8,
"version": 1
}
@@ -153,10 +153,8 @@ final class UserNotificationCenterDelegate: NSObject, UNUserNotificationCenterDe
}
private func waitUntilSessionBecomesActive(sessionId: String) async {
for await sessionState in sessionStatePublisher.values {
if (try? sessionState.userSession?.sessionId().get()) == sessionId {
break
}
for await sessionState in sessionStatePublisher.values where (try? sessionState.userSession?.sessionId().get()) == sessionId {
break
}
}
@@ -23,12 +23,13 @@ enum PreviewData {
extension LabelUIModel {
static func random(num: Int) -> [LabelUIModel] {
(0..<num).map { _ in
LabelUIModel(
labelId: .random(),
text: ["a", "b", "c"].randomElement()!,
color: [Color.blue, .red, .green].randomElement()!
)
}
(0..<num)
.map { _ in
LabelUIModel(
labelId: .random(),
text: ["a", "b", "c"].randomElement()!,
color: [Color.blue, .red, .green].randomElement()!
)
}
}
}
+2 -1
View File
@@ -89,7 +89,8 @@ final class AppContext: Sendable, ObservableObject {
hvNotifier: accountChallengeCoordinator,
deviceInfoProvider: ChallengePayloadProvider(),
issueReporter: SentryIssueReporter()
).get()
)
.get()
excludeDirectoriesFromBackup(params: params)
@@ -128,39 +128,40 @@ final class MailboxModel: ObservableObject {
extension MailboxModel {
private func setUpBindings() {
appRoute.$route.sink { [weak self] route in
guard let self else { return }
appRoute.$route
.sink { [weak self] route in
guard let self else { return }
switch route {
case .mailbox(selectedMailbox: let newSelectedMailbox):
guard newSelectedMailbox != selectedMailbox else {
return
}
switch route {
case .mailbox(selectedMailbox: let newSelectedMailbox):
guard newSelectedMailbox != selectedMailbox else {
return
}
Task {
self.selectionMode.selectionModifier.exitSelectionMode()
self.selectedMailbox = newSelectedMailbox
await self.updateMailboxAndScroller()
await self.prepareSwipeActions()
}
case .mailboxOpenMessage(seed: let openedItem):
state.isSearchPresented = false
replaceCurrentNavigationPath(with: openedItem)
case .composer(let fromShareExtension):
state.isSearchPresented = false
Task {
self.selectionMode.selectionModifier.exitSelectionMode()
self.selectedMailbox = newSelectedMailbox
await self.updateMailboxAndScroller()
await self.prepareSwipeActions()
}
case .mailboxOpenMessage(seed: let openedItem):
state.isSearchPresented = false
replaceCurrentNavigationPath(with: openedItem)
case .composer(let fromShareExtension):
state.isSearchPresented = false
if fromShareExtension {
openDraftForShareExtension()
} else {
createDraft()
if fromShareExtension {
openDraftForShareExtension()
} else {
createDraft()
}
case .mailto(let mailtoURL):
createDraft(with: mailtoURL)
case .search:
state.isSearchPresented = true
}
case .mailto(let mailtoURL):
createDraft(with: mailtoURL)
case .search:
state.isSearchPresented = true
}
}
.store(in: &cancellables)
.store(in: &cancellables)
Publishers.Merge(
mailSettingsLiveQuery.settingHasChanged(keyPath: \.swipeLeft),
@@ -312,14 +313,16 @@ extension MailboxModel {
callback: MessageScrollerLiveQueryCallbackWrapper { [weak self] update in
self?.scrollerUpdates.enqueueUpdate(update)
}
).get()
)
.get()
} else {
conversationScroller = try await scrollConversationsForLabel(
mailbox: mailbox,
callback: ConversationScrollerLiveQueryCallbackWrapper { [weak self] update in
self?.scrollerUpdates.enqueueUpdate(update)
}
).get()
)
.get()
}
paginatedDataSource.fetchInitialPage()
@@ -406,7 +409,7 @@ extension MailboxModel {
case .append(let conversations):
let items = await mailboxItems(conversations: conversations)
updateType = .append(items: items)
case let .replaceRange(from, to, conversations):
case .replaceRange(let from, let to, let conversations):
let items = await mailboxItems(conversations: conversations)
updateType = .replaceRange(from: Int(from), to: Int(to), items: items)
completion = { [weak self] in self?.updateSelectedItemsAfterDestructiveUpdate() }
@@ -451,7 +454,7 @@ extension MailboxModel {
case .append(let messages):
let items = await mailboxItems(messages: messages)
updateType = .append(items: items)
case let .replaceRange(from, to, messages):
case .replaceRange(let from, let to, let messages):
let items = await mailboxItems(messages: messages)
updateType = .replaceRange(from: Int(from), to: Int(to), items: items)
completion = { [weak self] in self?.updateSelectedItemsAfterDestructiveUpdate() }
@@ -547,7 +550,8 @@ extension MailboxModel {
showLocation: showLocation
)
}
}.value
}
.value
}
private func mailboxItems(conversations: [Conversation]) async -> [MailboxItemCellUIModel] {
@@ -558,7 +562,8 @@ extension MailboxModel {
conversations.map { conversation in
conversation.toMailboxItemCellUIModel(selectedIds: selectedIds, showLocation: showLocation)
}
}.value
}
.value
}
}
@@ -690,11 +695,12 @@ extension MailboxModel {
func onMailboxItemAction(_ context: SwipeActionContext, toastStateStore: ToastStateStore) {
guard let mailbox,
let output = swipeActionsHandler?.handle(
context,
toastStateStore: toastStateStore,
viewMode: mailbox.viewMode()
)
let output = swipeActionsHandler?
.handle(
context,
toastStateStore: toastStateStore,
viewMode: mailbox.viewMode()
)
else { return }
switch output.sheetType {
case .labelAs:
@@ -92,7 +92,8 @@ struct ConversationActionsMenu<OpenMenuButtonContent: View>: View {
actions = try await allAvailableConversationActionsForActionSheet(
mailbox: mailbox,
conversationId: conversationID
).get()
)
.get()
} catch {
AppLogger.log(error: error, category: .conversationDetail)
}
@@ -28,6 +28,6 @@ extension DraftProvider {
}
static var dummy: Self {
.init(makeDraft: { _, _ in return NewDraftResult.ok(.init(noPointer: .init())) })
.init(makeDraft: { _, _ in NewDraftResult.ok(.init(noPointer: .init())) })
}
}
@@ -43,7 +43,8 @@ struct LabelAsActionPerformer {
input.selectedLabelsIDs,
input.partiallySelectedLabelsIDs,
input.archive
).get()
)
.get()
return output
}
@@ -29,7 +29,7 @@ final class SendResultPresenter {
private typealias MessageID = ID
private let regularDuration: Toast.Duration = .short
private let extendedDuration: TimeInterval = 3.0
private var toasts = [MessageID: Toast]()
private var toasts: [MessageID: Toast] = [:]
private let subject = PassthroughSubject<SendResultToastAction, Never>()
private let draftPresenter: DraftPresenter
@@ -84,10 +84,12 @@ class SnoozeStore: StateStore {
private func loadSnoozeData() async {
do {
let snoozeActions = try await snoozeService.availableSnoozeActions(
for: state.conversationIDs,
systemCalendarWeekStart: DateEnvironment.calendar.nonDefaultWeekStart
).get()
let snoozeActions =
try await snoozeService.availableSnoozeActions(
for: state.conversationIDs,
systemCalendarWeekStart: DateEnvironment.calendar.nonDefaultWeekStart
)
.get()
state = state.copy(\.snoozeActions, to: snoozeActions)
} catch {
@@ -98,11 +100,13 @@ class SnoozeStore: StateStore {
private func snoozeConversations(snoozeTime: UnixTimestamp) async {
do {
_ = try await snoozeService.snooze(
conversation: state.conversationIDs,
labelId: state.labelId,
timestamp: snoozeTime
).get()
_ =
try await snoozeService.snooze(
conversation: state.conversationIDs,
labelId: state.labelId,
timestamp: snoozeTime
)
.get()
toastStateStore.present(toast: .snooze(snoozeDate: snoozeTime.date))
dismiss()
} catch {
@@ -113,10 +117,12 @@ class SnoozeStore: StateStore {
private func unsnoozeConversations() async {
do {
_ = try await snoozeService.unsnooze(
conversation: state.conversationIDs,
labelId: state.labelId
).get()
_ =
try await snoozeService.unsnooze(
conversation: state.conversationIDs,
labelId: state.labelId
)
.get()
toastStateStore.present(toast: .unsnooze)
dismiss()
} catch {
@@ -37,7 +37,7 @@ struct UndoScheduleSendProvider {
}
static func mockInstance(
stubbedResult: DraftCancelScheduleSendResult = .ok(.init(lastScheduledTime: 1747728129))
stubbedResult: DraftCancelScheduleSendResult = .ok(.init(lastScheduledTime: 1_747_728_129))
) -> UndoScheduleSendProvider {
.init(undoScheduleSend: { _ in stubbedResult })
}
@@ -81,7 +81,8 @@ struct AppProtectionSelectionScreen: View {
) {
store.handle(action: .autoLockTapped)
}
}.animation(.easeInOut, value: state.shouldShowAutoLockButton)
}
.animation(.easeInOut, value: state.shouldShowAutoLockButton)
}
Spacer()
}
@@ -52,7 +52,8 @@ struct CustomizeToolbarsScreen: View {
}
.padding(.horizontal, DS.Spacing.large)
.padding(.bottom, DS.Spacing.extraLarge)
}.onAppear {
}
.onAppear {
store.handle(action: .onAppear)
}
.onChange(
@@ -67,9 +67,10 @@ class EditToolbarStore: StateStore {
to: .init(selected: selectedList, unselected: unselectedList))
case .onLoad:
do {
let actions = try await customizeToolbarRepository.fetchActions()[
keyPath: state.toolbarType.actionsKeyPath
]
let actions =
try await customizeToolbarRepository.fetchActions()[
keyPath: state.toolbarType.actionsKeyPath
]
state = state.copy(\.toolbarActions, to: actions)
} catch {
AppLogger.log(error: error, category: .customizeToolbar)
@@ -35,7 +35,8 @@ struct PINRouterView: View {
view(route: route)
.navigationBarBackButtonHidden()
}
}.environmentObject(router)
}
.environmentObject(router)
}
private var navigationPath: Binding<[PINRoute]> {
@@ -95,25 +95,25 @@ final class MessageBodyStateStore: StateStore {
case .onLoad:
await loadMessageBody(with: .init())
case .refreshBanners:
if case let .loaded(body, _) = state.body {
if case .loaded(let body, _) = state.body {
await loadMessageBody(with: body.html.options)
}
case .displayEmbeddedImages:
if case let .loaded(body, _) = state.body {
if case .loaded(let body, _) = state.body {
let updatedOptions = body.html.options
.copy(\.hideEmbeddedImages, to: false)
await loadMessageBody(with: updatedOptions)
}
case .downloadRemoteContent:
if case let .loaded(body, _) = state.body {
if case .loaded(let body, _) = state.body {
let updatedOptions = body.html.options
.copy(\.hideRemoteImages, to: false)
await loadMessageBody(with: updatedOptions)
}
case .reloadFailedProxyImages:
if case let .loaded(body, _) = state.body {
if case .loaded(let body, _) = state.body {
var newBanners = state.eventBanners
newBanners.remove(.proxyImageLoadFail)
state =
@@ -131,11 +131,11 @@ final class MessageBodyStateStore: StateStore {
case .markAsLegitimateConfirmed(let action):
state = state.copy(\.alert, to: nil)
if case let .loaded(body, _) = state.body, case .markAsLegitimate = action {
if case .loaded(let body, _) = state.body, case .markAsLegitimate = action {
await markAsLegitimate(with: body.html.options)
}
case .unblockSender(let emailAddress):
if case let .loaded(body, _) = state.body {
if case .loaded(let body, _) = state.body {
await unblockSender(emailAddress: emailAddress, with: body.html.options)
}
case .unsubscribeNewsletter:
@@ -146,7 +146,7 @@ final class MessageBodyStateStore: StateStore {
case .unsubscribeNewsletterConfirmed(let action):
state = state.copy(\.alert, to: nil)
if case let .loaded(body, _) = state.body, case .unsubscribe = action {
if case .loaded(let body, _) = state.body, case .unsubscribe = action {
await unsubscribeNewsletter(with: body.newsletterService, options: body.html.options)
}
}
@@ -173,5 +173,6 @@ enum ExpandedMessageCellEvent {
onEvent: { _ in },
htmlDisplayed: {}
)
}.environmentObject(ToastStateStore(initialState: .initial))
}
.environmentObject(ToastStateStore(initialState: .initial))
}
@@ -139,7 +139,7 @@ struct MessageBodyAttachmentsView: View {
private extension Array where Element == AttachmentDisplayModel {
var totalSize: Int64 {
reduce(0) { result, next in
return result + Int64(next.size)
result + Int64(next.size)
}
}
}
@@ -542,7 +542,7 @@ enum MessageDetailsPreviewProvider {
recipientsTo: recipientsTo,
recipientsCc: recipientsCc,
recipientsBcc: recipientsBcc,
date: Date(timeIntervalSince1970: 1724347300),
date: Date(timeIntervalSince1970: 1_724_347_300),
location: location?.model,
labels: labels,
isStarred: false,
@@ -420,12 +420,13 @@ final class ConversationDetailModel: Sendable, ObservableObject {
let alert: AlertModel = .deleteConfirmation(
itemsCount: 1,
action: { [weak self] action in
await self?.handle(
id: conversationID,
mailboxItem: .conversation,
action: action,
toastStateStore: toastStateStore, goBack: goBack
)
await self?
.handle(
id: conversationID,
mailboxItem: .conversation,
action: action,
toastStateStore: toastStateStore, goBack: goBack
)
}
)
actionAlert = alert
@@ -758,7 +759,8 @@ extension ConversationDetailModel {
mailbox: mailbox,
id: conversationID,
showAll: showAllMessages
).get()
)
.get()
let hiddenMessagesBanner = conversationAndMessages?.conversation.hiddenMessagesBanner
let isStarred = conversationAndMessages?.conversation.isStarred ?? false
let messages = conversationAndMessages?.messages ?? []
@@ -846,7 +848,8 @@ extension ConversationDetailModel {
let actions = try await allAvailableConversationActionsForConversation(
mailbox: mailbox,
conversationId: conversationItem.id
).get()
)
.get()
self.conversationToolbarActions = .conversation(actions: actions, conversationID: conversationItem.id)
} catch {
AppLogger.log(error: error, category: .conversationDetail)
@@ -861,7 +864,8 @@ extension ConversationDetailModel {
mailbox: mailbox,
theme: theme,
messageId: conversationItem.id
).get()
)
.get()
self.conversationToolbarActions = .message(actions: actions, messageID: conversationItem.id)
} catch {
AppLogger.log(error: error, category: .conversationDetail)
@@ -44,7 +44,8 @@ private extension MailboxItemCellUIModel {
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()!
]
.randomElement()!
]
} else {
[]
@@ -244,7 +244,7 @@ final class SearchModel: ObservableObject {
case .append(let messages):
let items = await mailboxItems(messages: messages)
updateType = .append(items: items)
case let .replaceRange(from, to, messages):
case .replaceRange(let from, let to, let messages):
let items = await mailboxItems(messages: messages)
updateType = .replaceRange(from: Int(from), to: Int(to), items: items)
completion = { [weak self] in self?.updateSelectedItemsAfterDestructiveUpdate() }
@@ -274,7 +274,8 @@ final class SearchModel: ObservableObject {
showLocation: true
)
}
}.value
}
.value
}
func prepareSwipeActions() async {
@@ -116,7 +116,8 @@ struct SidebarScreen: View {
.padding(.vertical, DS.Spacing.small)
.onTapGesture(count: 5) { screenModel.handle(action: .logoTappedFiveTimes) }
separator
}.background(
}
.background(
GeometryReader { geometry in
BlurredBackground(fallbackBackgroundColor: DS.Color.Sidebar.background)
.edgesIgnoringSafeArea(.all)
@@ -225,11 +226,13 @@ struct SidebarScreen: View {
.padding(.vertical, DS.Spacing.medium)
separator
appVersionNote
}.onChange(of: appUIStateStore.sidebarState.isOpen) { _, isSidebarOpen in
}
.onChange(of: appUIStateStore.sidebarState.isOpen) { _, isSidebarOpen in
if isSidebarOpen, let first = screenModel.state.items.first {
proxy.scrollTo(first.id, anchor: .zero)
}
}.accessibilityElement(children: .contain)
}
.accessibilityElement(children: .contain)
}
.scrollDisabled(gestureState.lockedAxis == .horizontal || lastCommittedAxis == .horizontal)
.frame(maxWidth: .infinity)
@@ -160,7 +160,7 @@ private struct AttachmentCapsuleStyle: ButtonStyle {
}
}
fileprivate enum Layout {
private enum Layout {
static let spacingBetweenCapsules = DS.Spacing.tiny
static let extraAttachmentsViewWidth = 42.0
static let capsuleHPadding = DS.Spacing.standard
@@ -39,7 +39,8 @@ struct AvatarCheckboxView: View {
.padding(10)
.accessibilityIdentifier(AvatarCheckboxViewIdentifiers.avatarChecked)
}
}.accessibilityElement(children: .contain)
}
.accessibilityElement(children: .contain)
} else {
AvatarView(avatar: avatar)
}
@@ -279,12 +279,12 @@ private extension AssignedSwipeAction {
private extension AssignedSwipeAction {
func isDestructive(locationSystemLabel: SystemLabel?, itemSystemLabel: SystemLabel?) -> Bool {
guard case let .moveTo(location) = self else {
guard case .moveTo(let location) = self else {
return false
}
switch location {
case let .moveToSystemLabel(targetSystemLabel, _):
case .moveToSystemLabel(let targetSystemLabel, _):
switch locationSystemLabel {
case .allMail, .allSent, .allDrafts:
return false
@@ -51,7 +51,8 @@ struct OneLineLabelsListView: View {
}
}
}
}.frame(height: height)
}
.frame(height: height)
}
// MARK: - Private
@@ -99,5 +100,6 @@ struct OneLineLabelsListView: View {
OneLineLabelsListView(labels: labels)
}
Spacer()
}.padding()
}
.padding()
}
@@ -25,7 +25,8 @@ enum OneLineLabelsListViewPreviewDataProvider {
["😈"],
["Long long long long long long long long long long long long long long"],
["Aaaaaaaa", "Long long label long long long long long", "aaaaaaaaaaaaa"],
].map { $0.map(LabelUIModel.testData) }
]
.map { $0.map(LabelUIModel.testData) }
}
}
@@ -57,13 +57,13 @@ final class PaginatedListDataSource<Item: Equatable>: ObservableObject {
switch update.value {
case .append(let items):
newState.items.append(contentsOf: items)
case let .replaceRange(from, to, items):
case .replaceRange(let from, let to, let items):
guard isSafeIndex(from), isSafeIndex(to) else { break }
newState.items.replaceSubrange(from..<to, with: items)
case let .replaceFrom(index, items):
case .replaceFrom(let index, let items):
guard isSafeIndex(index) else { break }
newState.items.replaceSubrange(index..<newState.items.endIndex, with: items)
case let .replaceBefore(index, items):
case .replaceBefore(let index, let items):
guard isSafeIndex(index) else { break }
newState.items.replaceSubrange(newState.items.startIndex..<index, with: items)
case .none, .error:
@@ -94,9 +94,11 @@ class BackgroundTransitionActionsExecutor: ApplicationServiceDidEnterBackground,
Self.log("Internet connection on start: \(hasAccessToInternetOnStart == true ? "Online" : "Offline")")
do {
backgroundExecutionHandle = try backgroundTaskExecutorProvider().startBackgroundExecution(
callback: callback
).get()
backgroundExecutionHandle = try backgroundTaskExecutorProvider()
.startBackgroundExecution(
callback: callback
)
.get()
Self.log("Handle is returned, background actions in progress")
Self.log("Handle present: \(self.backgroundExecutionHandle != nil)?")
} catch {
@@ -21,8 +21,8 @@ import SwiftUI
/// Keeps `maxElements` in memory. When the limit is reached evicts from the cache the oldest element
final class MemoryCache<Key: Hashable & Sendable, Value: Sendable>: @unchecked Sendable {
private let maxElements: Int
private var dictionary = [Key: Value]()
private var fifoQueue = [Key]()
private var dictionary: [Key: Value] = [:]
private var fifoQueue: [Key] = []
private let queue = DispatchQueue(label: "\(Bundle.defaultIdentifier).MemoryCache", attributes: .concurrent)
@@ -26,7 +26,8 @@ extension UIApplication {
// Keep only the first `UIWindowScene`
.first(where: { $0 is UIWindowScene })
// Get its associated windows
.flatMap({ $0 as? UIWindowScene })?.windows
.flatMap({ $0 as? UIWindowScene })?
.windows
// Finally, keep only the key window
.first(where: \.isKeyWindow)
}
@@ -18,6 +18,6 @@
extension UInt64 {
// Timestamp 1753883097 = 2025-05-31 01:24:57 UTC
static var timestamp: UInt64 {
1753883097
1_753_883_097
}
}
@@ -34,7 +34,8 @@ final class MoveToSheetSnapshotTests {
moveToActions: .dummy,
navigation: { _ in },
mailUserSession: .dummy
).environmentObject(ToastStateStore(initialState: .initial))
)
.environmentObject(ToastStateStore(initialState: .initial))
assertSnapshotsOnIPhoneX(of: sut, named: "move_to_sheet")
}
}
@@ -59,7 +59,7 @@ private extension MailboxItemCellUIModel {
}
static func makeSimpleMessage(type: SimpleMessageType) -> MailboxItemCellUIModel {
let snoozeTime = Date(timeIntervalSince1970: 1878451200)
let snoozeTime = Date(timeIntervalSince1970: 1_878_451_200)
let expirationTime = Date(timeIntervalSince1970: Date.now.timeIntervalSince1970 + 3600 * 24 * 365 * 2)
return MailboxItemCellUIModel(
id: .random(),
@@ -71,7 +71,7 @@ private extension MailboxItemCellUIModel {
),
emails: "arya.lindt@example.com",
subject: "Making the most of Safari",
date: Date(timeIntervalSince1970: 1717485341),
date: Date(timeIntervalSince1970: 1_717_485_341),
location: nil,
locationIcon: type == .locationIcon ? DS.Icon.icInbox.image : nil,
isRead: false,
@@ -103,7 +103,7 @@ private extension MailboxItemCellUIModel {
),
emails: "Travel",
subject: "Your booking confirmation KL877N",
date: Date(timeIntervalSince1970: 1717483827),
date: Date(timeIntervalSince1970: 1_717_483_827),
location: nil,
locationIcon: nil,
isRead: true,
@@ -141,7 +141,7 @@ private extension MailboxItemCellUIModel {
),
emails: "Flights to Palo Alto - 20th of September, 2025",
subject: "You're invited to flight KCY877N",
date: Date(timeIntervalSince1970: 1717484927),
date: Date(timeIntervalSince1970: 1_717_484_927),
location: nil,
locationIcon: nil,
isRead: true,
@@ -173,7 +173,7 @@ private extension MailboxItemCellUIModel {
),
emails: "Jane Doe, Mike, Laureen Smith",
subject: "Photos from Portugal",
date: Date(timeIntervalSince1970: 1717484830),
date: Date(timeIntervalSince1970: 1_717_484_830),
location: nil,
locationIcon: nil,
isRead: true,
@@ -92,7 +92,7 @@ private extension SnoozeActions {
}
private extension SnoozeTime {
private static let timestamp: UInt64 = 1752697012
private static let timestamp: UInt64 = 1_752_697_012
static var tomorrow: Self {
.tomorrow(timestamp)
@@ -24,11 +24,12 @@ import UIKit
final class AppDelegateTests {
@Test
func testSceneConfiguration_WhenConnectingSceneSession_HasCustomSceneDelegateConfigured() throws {
let sceneConfiguration = AppDelegate().application(
.shared,
configurationForConnecting: try scene(),
options: try connectionOptions()
)
let sceneConfiguration = AppDelegate()
.application(
.shared,
configurationForConnecting: try scene(),
options: try connectionOptions()
)
#expect(sceneConfiguration.delegateClass === SceneDelegate.self)
}
@@ -67,7 +67,7 @@ final class SceneDelegateTests: BaseTestCase {
let overlayWindow = try XCTUnwrap(sut.overlayWindow)
XCTAssert(overlayWindow is PassThroughWindow)
XCTAssert(overlayWindow.rootViewController is UIHostingController<ModifiedContent<ToastSceneView, _EnvironmentKeyWritingModifier<Optional<ToastStateStore>>>>)
XCTAssert(overlayWindow.rootViewController is UIHostingController<ModifiedContent<ToastSceneView, _EnvironmentKeyWritingModifier<ToastStateStore?>>>)
XCTAssertEqual(overlayWindow.rootViewController?.view.backgroundColor, .clear)
XCTAssertFalse(overlayWindow.isHidden)
}
File diff suppressed because one or more lines are too long
@@ -74,7 +74,8 @@ private extension MainKeyUnlockerTests {
.init(
base64Encoded:
"dAcGOBeHqCMJvQPyOOy303bveHdY+QmCt8RpD6xX8u6+7PLF3pnUXhn91fIb2UND5P7Se8wKkKboY9a9ayFOJMm9uviXe6jCnT9C9Mh8rT3Bn04ctKPIg1YwZXCQwz80kQ/y/tW8wWACS4xRJ70v2MG5nh9jCGsi2nZ3PfFuX4545dfK0H6K0IpdwYYaZqWT6WJrGr6x+QGkJZZc6qfzLvZ7O7lmenuzc2u/fS7+fRouUROQW/2O7bo="
).unsafelyUnwrapped
)
.unsafelyUnwrapped
}
var pinProtectionSalt: Data {
@@ -280,7 +280,7 @@ extension DraftPresenterTests {
func makeSUT(
stubbedNewDraftResult: NewDraftResult = .ok(.dummyDraft),
stubbedUndoSendError: DraftUndoSendError? = nil,
stubbedCancelScheduleResult: DraftCancelScheduleSendResult = .ok(.init(lastScheduledTime: 1747728129))
stubbedCancelScheduleResult: DraftCancelScheduleSendResult = .ok(.init(lastScheduledTime: 1_747_728_129))
) -> DraftPresenter {
DraftPresenter(
userSession: MailUserSessionSpy(id: ""),
@@ -344,10 +344,8 @@ private extension Array where Element == SendResultToastAction {
func isSame(as other: [SendResultToastAction]) -> Bool {
guard self.count == other.count else { return false }
for (action1, action2) in zip(self, other) {
if !action1.isSame(as: action2) {
return false
}
for (action1, action2) in zip(self, other) where !action1.isSame(as: action2) {
return false
}
return true
}
@@ -105,7 +105,8 @@ final class SidebarModelTests {
func test_WhenCustomFolderIsExpandedAndCollapsed_ItExpandsAndCollapsesTheFolder() throws {
let parentFolder = try XCTUnwrap(
sidebarSpy.stubbedCustomFolders.first(where: { !$0.children.isEmpty })
).sidebarFolder
)
.sidebarFolder
XCTAssertEqual(sidebarSpy.expandFolderInvoked, [])
XCTAssertEqual(sidebarSpy.collapseFolderInvoked, [])
@@ -144,6 +145,7 @@ extension SidebarState {
func find(folderWithName name: String, in folders: [SidebarFolder]) -> SidebarFolder? {
folders.compactMap { folder in
folder.name == name ? folder : find(folderWithName: name, in: folder.childFolders)
}.first
}
.first
}
}
@@ -21,7 +21,7 @@ import Testing
struct MessageExpiryTimeFormatterTests {
enum Timestamp: Int {
case _2025_02_22_15_30_00 = 1740238200
case _2025_02_22_15_30_00 = 1_740_238_200
}
@Test(
@@ -35,7 +35,7 @@ final class ConversationDetailHeaderMainTests: PMUIMockedNetworkTestCase {
index: 0,
senderName: "Not Proton",
senderAddress: "no-reply@not.proton.black",
timestamp: 1716199297,
timestamp: 1_716_199_297,
toRecipients: [UITestHeaderRecipientEntry(index: 0, name: "youngbee@proton.black", address: "youngbee@proton.black")]
)
@@ -58,7 +58,7 @@ final class ConversationDetailHeaderMultipleFieldsTests: PMUIMockedNetworkTestCa
index: 0,
senderName: "Test Free Account",
senderAddress: "notsofree@proton.black",
timestamp: 1718884640,
timestamp: 1_718_884_640,
toRecipients: [
UITestHeaderRecipientEntry(index: 0, name: "plus@proton.black", address: "plus@proton.black")
],
@@ -71,7 +71,7 @@ final class ConversationDetailHeaderMultipleFieldsTests: PMUIMockedNetworkTestCa
index: 1,
senderName: "Young Bee",
senderAddress: "youngbee@proton.black",
timestamp: 1718885017,
timestamp: 1_718_885_017,
toRecipients: [
UITestHeaderRecipientEntry(index: 0, name: "Test Free Account", address: "notsofree@proton.black")
],
@@ -84,7 +84,7 @@ final class ConversationDetailHeaderMultipleFieldsTests: PMUIMockedNetworkTestCa
index: 2,
senderName: "Young Bee",
senderAddress: "youngbee@proton.black",
timestamp: 1718976443,
timestamp: 1_718_976_443,
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"),
@@ -33,14 +33,17 @@ final class MockServer: Sendable {
value: 1
)
.childChannelInitializer { channel in
channel.pipeline.configureHTTPServerPipeline(
withPipeliningAssistance: false,
withErrorHandling: false
).flatMap { _ in
channel.pipeline.addHandler(BackPressureHandler()).flatMap { item in
channel.pipeline.addHandler(self.requestsHandler)
channel.pipeline
.configureHTTPServerPipeline(
withPipeliningAssistance: false,
withErrorHandling: false
)
.flatMap { _ in
channel.pipeline.addHandler(BackPressureHandler())
.flatMap { item in
channel.pipeline.addHandler(self.requestsHandler)
}
}
}
}
.childChannelOption(ChannelOptions.socket(IPPROTO_TCP, TCP_NODELAY), value: 1)
.childChannelOption(
@@ -121,11 +121,13 @@ extension RequestsHandler {
promise: nil
)
context.channel.writeAndFlush(
self.wrapOutboundOut(HTTPServerResponsePart.end(nil))
).whenComplete { _ in
/* no op */
}
context.channel
.writeAndFlush(
self.wrapOutboundOut(HTTPServerResponsePart.end(nil))
)
.whenComplete { _ in
/* no op */
}
}
}
}
@@ -52,9 +52,10 @@ extension RequestsHandlerActor {
.first { mockRequest in
switch true {
case mockRequest.ignoreQueryParams && mockRequest.wildcardMatch:
return clientRequest.withStrippedQueryParams().wildcardMatches(
mockRequest.remoteRequest
)
return clientRequest.withStrippedQueryParams()
.wildcardMatches(
mockRequest.remoteRequest
)
case mockRequest.ignoreQueryParams:
return clientRequest.stripPathQueryParams() == mockRequest.remoteRequest.path
case mockRequest.wildcardMatch:
@@ -44,7 +44,7 @@ public final class ComposerContactProvider {
task?.cancel()
task = Task {
let textForMatching = text.toContactMatchFormat()
var matchingContacts = [ComposerContact]()
var matchingContacts: [ComposerContact] = []
if let contactsResult {
matchingContacts = contactsResult.filter(textForMatching)
@@ -244,30 +244,32 @@ final class MockComposerRecipientList: ComposerRecipientListProtocol, @unchecked
}
final class MockAttachmentList: AttachmentListProtocol, @unchecked Sendable {
var mockAttachments = [DraftAttachment]()
var mockAttachments: [DraftAttachment] = []
var attachmentUploadDirectoryURL: URL = URL(fileURLWithPath: .empty)
var capturedAddCalls: [(path: String, filenameOverride: String?)] = []
var capturedAddInlineCalls: [(path: String, filenameOverride: String?)] = []
var capturedSwapInlineCalls: [String] = []
var capturedRemoveIdCalls: [ID] = []
var capturedRemoveContentIdCalls: [String] = []
var mockAttachmentListAddResult = [(lastPathComponent: String, result: AttachmentListAddResult)]()
var mockAttachmentListAddInlineResult = [(lastPathComponent: String, result: AttachmentListAddInlineResult)]()
var mockAttachmentListAddResult: [(lastPathComponent: String, result: AttachmentListAddResult)] = []
var mockAttachmentListAddInlineResult: [(lastPathComponent: String, result: AttachmentListAddInlineResult)] = []
var mockAttachmentSwapWithCidResult: VoidDraftAttachmentDispositionSwapResult = .ok
var mockAttachmentListRemoveWithCidResult = [(cid: String, result: AttachmentListRemoveWithCidResult)]()
var mockAttachmentListRemoveWithCidResult: [(cid: String, result: AttachmentListRemoveWithCidResult)] = []
func add(path: String, filenameOverride: String?) async -> AttachmentListAddResult {
capturedAddCalls.append((path, filenameOverride))
return mockAttachmentListAddResult.first(where: {
$0.lastPathComponent == path.suffix($0.lastPathComponent.count)
})?.result ?? AttachmentListAddResult.ok
})?
.result ?? AttachmentListAddResult.ok
}
func addInline(path: String, filenameOverride: String?) async -> AttachmentListAddInlineResult {
capturedAddInlineCalls.append((path, filenameOverride))
return mockAttachmentListAddInlineResult.first(where: {
$0.lastPathComponent == path.suffix($0.lastPathComponent.count)
})?.result ?? AttachmentListAddInlineResult.ok("12345")
})?
.result ?? AttachmentListAddInlineResult.ok("12345")
}
func attachmentUploadDirectory() -> String {
@@ -287,7 +289,8 @@ final class MockAttachmentList: AttachmentListProtocol, @unchecked Sendable {
capturedRemoveContentIdCalls.append(contentId)
return mockAttachmentListRemoveWithCidResult.first(where: {
$0.cid == contentId
})?.result ?? AttachmentListRemoveWithCidResult.ok
})?
.result ?? AttachmentListRemoveWithCidResult.ok
}
func retry(attachmentId: Id) async -> AttachmentListRetryResult {
@@ -95,7 +95,7 @@ extension Array where Element == RecipientUIModel {
func extractAddressesThatDoNotExist(from array: [RecipientUIModel]) -> Set<String> {
Set(
array.compactMap { model in
if case let .single(singleRecipient) = model.composerRecipient,
if case .single(let singleRecipient) = model.composerRecipient,
case .invalid(.doesNotExist) = singleRecipient.validState
{
return singleRecipient.address
@@ -140,10 +140,10 @@ extension DraftAttachmentsSectionViewController {
}
static let uiModels: [DraftAttachmentUIModel] = [
Model.makeUIModel(id: 1, name: "meeting_minutes_for_last_friday.pdf", cat: .pdf, size: 36123512, state: .uploading),
Model.makeUIModel(id: 1, name: "meeting_minutes_for_last_friday.pdf", cat: .pdf, size: 36_123_512, state: .uploading),
Model.makeUIModel(id: 2, name: "budget.xls", cat: .excel, size: 263478, state: .uploaded),
Model.makeUIModel(id: 3, name: "photo_1.jpg", cat: .image, size: 7824333, state: .offline),
Model.makeUIModel(id: 4, name: "photo_2_this_one_a_bit_closer.jpg", cat: .image, size: 6123512, state: .uploaded),
Model.makeUIModel(id: 3, name: "photo_1.jpg", cat: .image, size: 7_824_333, state: .offline),
Model.makeUIModel(id: 4, name: "photo_2_this_one_a_bit_closer.jpg", cat: .image, size: 6_123_512, state: .uploaded),
]
}
@@ -64,7 +64,8 @@ final class SpinnerButton: UIButton {
startAngle: 0,
endAngle: CGFloat.pi * 3 / 2,
clockwise: true
).cgPath
)
.cgPath
progressLayer.position = CGPoint(x: bounds.midX, y: bounds.midY)
progressLayer.path = circlePath
@@ -58,12 +58,13 @@ final class ContactPickerCell: UITableViewCell {
checked.heightAnchor.constraint(equalToConstant: 24),
])
[initials, groupIcon].forEach {
NSLayoutConstraint.activate([
$0.centerXAnchor.constraint(equalTo: avatarView.centerXAnchor),
$0.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor),
])
}
[initials, groupIcon]
.forEach {
NSLayoutConstraint.activate([
$0.centerXAnchor.constraint(equalTo: avatarView.centerXAnchor),
$0.centerYAnchor.constraint(equalTo: avatarView.centerYAnchor),
])
}
}
func configure(uiModel: ComposerContactUIModel) {
@@ -105,12 +105,13 @@ final class DraftActionBarViewController: UIViewController {
topBorder.trailingAnchor.constraint(equalTo: view.trailingAnchor),
topBorder.topAnchor.constraint(equalTo: view.topAnchor),
])
[attachmentButton, passwordButton, discardButton].forEach { button in
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: buttonSize),
button.heightAnchor.constraint(equalTo: button.widthAnchor),
])
}
[attachmentButton, passwordButton, discardButton]
.forEach { button in
NSLayoutConstraint.activate([
button.widthAnchor.constraint(equalToConstant: buttonSize),
button.heightAnchor.constraint(equalTo: button.widthAnchor),
])
}
}
private func applyState() {
@@ -92,7 +92,7 @@ extension AttachmentErrorAlertState {
}
}
var result = [AttachmentErrorAlertModel]()
var result: [AttachmentErrorAlertModel] = []
if overSizeLimitCount > 0 {
result.append(.overSizeLimit(origin: .adding(.defaultAddAttachmentError(count: overSizeLimitCount))))
}
@@ -110,10 +110,10 @@ extension AttachmentErrorAlertState {
/// Groups together `DraftAttachment` by error type to reduce the total number of alerts.
private func aggregateUploadingAttachmentErrors(_ attachments: [DraftAttachment]) -> [AttachmentErrorAlertModel] {
var overSizeFailures = [DraftAttachment]()
var tooManyAttachmentsFailures = [DraftAttachment]()
var storageQuotaExceededFailures = [DraftAttachment]()
var otherFailures = [DraftAttachment]()
var overSizeFailures: [DraftAttachment] = []
var tooManyAttachmentsFailures: [DraftAttachment] = []
var storageQuotaExceededFailures: [DraftAttachment] = []
var otherFailures: [DraftAttachment] = []
for attachment in attachments {
guard let error = attachment.state.attachmentUploadError else { continue }
@@ -136,7 +136,7 @@ extension AttachmentErrorAlertState {
}
}
var result = [AttachmentErrorAlertModel]()
var result: [AttachmentErrorAlertModel] = []
if !overSizeFailures.isEmpty, let uploadingError = mapUnseenToUploadingErrorOrigin(overSizeFailures) {
result.append(.overSizeLimit(origin: uploadingError))
}
@@ -31,7 +31,7 @@ struct FilePickerItemHandler {
let uploadFolder: URL = URL(fileURLWithPath: draft.attachmentList().attachmentUploadDirectory())
switch selectionResult {
case .success(let urls):
var allErrors = [DraftAttachmentUploadError]()
var allErrors: [DraftAttachmentUploadError] = []
for await result in copyFilePickerItems(files: urls, destinationFolder: uploadFolder) {
switch result {
case .success(let file):
@@ -30,8 +30,8 @@ struct PhotosPickerItemHandler {
to draft: AppDraftProtocol,
photos: [PhotosPickerItemTransferable]
) async -> PhotosPickerItemHandlerResult {
var successfulContentIds = [String]()
var allErrors = [DraftAttachmentUploadError]()
var successfulContentIds: [String] = []
var allErrors: [DraftAttachmentUploadError] = []
let uploadFolder: URL = URL(fileURLWithPath: draft.attachmentList().attachmentUploadDirectory())
for await result in saveToFile(items: photos, destinationFolder: uploadFolder) {
switch result {
@@ -58,8 +58,8 @@ final class ComposerModel: ObservableObject {
)
lazy var invalidAddressAlertStore = InvalidAddressAlertStateStore(
validator: .init(
readState: { [weak self] in return self?.state },
composerWillDismiss: { [weak self] in return self?.composerWillDismiss ?? false }
readState: { [weak self] in self?.state },
composerWillDismiss: { [weak self] in self?.composerWillDismiss ?? false }
),
alertBinding: alertBinding
)
@@ -160,11 +160,12 @@ final class ComposerModel: ObservableObject {
recipientFieldState.copy(\.controllerState, to: .editing)
}
newState = newState.copy(\.editingRecipientsGroup, to: group)
RecipientGroupType.allCases(excluding: group).forEach { group in
newState.overrideRecipientState(for: group) { recipientFieldState in
recipientFieldState.copy(\.controllerState, to: .expanded)
RecipientGroupType.allCases(excluding: group)
.forEach { group in
newState.overrideRecipientState(for: group) { recipientFieldState in
recipientFieldState.copy(\.controllerState, to: .expanded)
}
}
}
state = newState
}
@@ -521,9 +522,10 @@ extension ComposerModel {
selecting selectedIndexes: Set<Int> = []
) -> [RecipientUIModel] {
let recipientList = recipientList(from: draft, group: group)
return recipientList.recipients().enumerated().map { index, recipient in
RecipientUIModel(composerRecipient: recipient, isSelected: selectedIndexes.contains(index))
}
return recipientList.recipients().enumerated()
.map { index, recipient in
RecipientUIModel(composerRecipient: recipient, isSelected: selectedIndexes.contains(index))
}
}
private func setUpCallbacks() {
@@ -88,7 +88,7 @@ struct ComposerView: View {
case .viewDidDisappear:
Task { await model.viewDidDisappear() }
case let .recipientFieldEvent(recipientFieldEvent, group):
case .recipientFieldEvent(let recipientFieldEvent, let group):
switch recipientFieldEvent {
case .onFieldTap:
model.startEditingRecipients(for: group)
@@ -104,7 +104,7 @@ struct ComposerView: View {
model.removeRecipientsThatAreSelected(group: group)
}
case let .contactPickerEvent(event, group):
case .contactPickerEvent(let event, let group):
switch event {
case .onInputChange(let text):
model.matchContact(group: group, text: text)
@@ -192,7 +192,7 @@ private extension View {
)
ScheduleSendTimeOptionsView(
predefinedTimeOptions: options(true, 1904565584),
predefinedTimeOptions: options(true, 1_904_565_584),
isCustomOptionAvailable: true,
dateFormatter: .init(),
onTimeSelected: { _ in },
@@ -20,8 +20,10 @@ struct InlineImageHTML {
let content: String
init(cids: [String]) {
content = cids.map { cid in
#"<img src="cid:\#(cid)" style="max-width: 100%;"><br>"#
}.joined()
content =
cids.map { cid in
#"<img src="cid:\#(cid)" style="max-width: 100%;"><br>"#
}
.joined()
}
}
@@ -29,11 +29,13 @@ final class ScheduleSendSheetSnapshotTests {
@Test
func testScheduleSend_whenFreeUser_itLayoutsCorrectOnIphoneX() throws {
let options: DraftScheduleSendOptions = try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: false,
stubTomorrowTime: 1810210838,
stubMondayTime: 1810729238
).scheduleSendOptions().get()
let options: DraftScheduleSendOptions =
try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: false,
stubTomorrowTime: 1_810_210_838,
stubMondayTime: 1_810_729_238
)
.scheduleSendOptions().get()
let scheduleSend = ScheduleSendPickerSheet(
predefinedTimeOptions: options.toScheduleSendTimeOptions(lastScheduleSendTime: nil),
isCustomOptionAvailable: options.isCustomOptionAvailable,
@@ -45,11 +47,13 @@ final class ScheduleSendSheetSnapshotTests {
@Test
func testScheduleSend_whenPaidUser_itLayoutsCorrectOnIphoneX() throws {
let options: DraftScheduleSendOptions = try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: true,
stubTomorrowTime: 1810210838,
stubMondayTime: 1810729238
).scheduleSendOptions().get()
let options: DraftScheduleSendOptions =
try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: true,
stubTomorrowTime: 1_810_210_838,
stubMondayTime: 1_810_729_238
)
.scheduleSendOptions().get()
let scheduleSend = ScheduleSendPickerSheet(
predefinedTimeOptions: options.toScheduleSendTimeOptions(lastScheduleSendTime: nil),
isCustomOptionAvailable: options.isCustomOptionAvailable,
@@ -61,13 +65,15 @@ final class ScheduleSendSheetSnapshotTests {
@Test
func testScheduleSend_whenPaidUser_andPreviouslySetTime_itLayoutsCorrectOnIphoneX() throws {
let options: DraftScheduleSendOptions = try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: true,
stubTomorrowTime: 1810210838,
stubMondayTime: 1810729238
).scheduleSendOptions().get()
let options: DraftScheduleSendOptions =
try ScheduleSendOptionsProvider.dummy(
isCustomAvailable: true,
stubTomorrowTime: 1_810_210_838,
stubMondayTime: 1_810_729_238
)
.scheduleSendOptions().get()
let scheduleSend = ScheduleSendPickerSheet(
predefinedTimeOptions: options.toScheduleSendTimeOptions(lastScheduleSendTime: 1810483200),
predefinedTimeOptions: options.toScheduleSendTimeOptions(lastScheduleSendTime: 1_810_483_200),
isCustomOptionAvailable: options.isCustomOptionAvailable,
dateFormatter: dateFormatter,
onTimeSelected: { _ in }
@@ -23,9 +23,9 @@ import proton_app_uniffi
@MainActor
final class AttachmentErrorAlertStateTests {
private var sut: AttachmentErrorAlertState!
private static let timeStamp1: Int64 = 1743000520
private static let timeStamp2: Int64 = 1743009702
private static let timeStamp3: Int64 = 1743032002
private static let timeStamp1: Int64 = 1_743_000_520
private static let timeStamp2: Int64 = 1_743_009_702
private static let timeStamp3: Int64 = 1_743_032_002
private let tooManyError1 = DraftAttachment.makeMock(id: 1, state: .error(.upload(.reason(.tooManyAttachments))), timestamp: timeStamp1)
private let tooManyError2 = DraftAttachment.makeMock(id: 2, state: .error(.upload(.reason(.tooManyAttachments))), timestamp: timeStamp2)
private let tooLargeError1 = DraftAttachment.makeMock(id: 3, state: .error(.upload(.reason(.attachmentTooLarge))), timestamp: timeStamp3)
@@ -797,7 +797,7 @@ final class ComposerModelTests: BaseTestCase {
let sut = makeSut(draft: mockDraft, draftOrigin: .new, contactProvider: .mockInstance)
let dismissSpy = DismissSpy()
let scheduleTime: UInt64 = 1905427712
let scheduleTime: UInt64 = 1_905_427_712
await sut.sendMessage(at: scheduleTime.date, dismissAction: dismissSpy)
XCTAssertTrue(mockDraft.scheduleSendWasCalled)
@@ -813,7 +813,7 @@ final class ComposerModelTests: BaseTestCase {
let sut = makeSut(draft: mockDraft, draftOrigin: .new, contactProvider: .mockInstance)
let dismissSpy = DismissSpy()
let scheduleTime: UInt64 = 1905427712
let scheduleTime: UInt64 = 1_905_427_712
await sut.sendMessage(at: scheduleTime.date, dismissAction: dismissSpy)
XCTAssertTrue(mockDraft.scheduleSendWasCalled)
@@ -943,11 +943,12 @@ private extension ComposerModelTests {
}
func fulfill(_ expectation: XCTestExpectation, in sut: ComposerModel, when condition: @escaping (ComposerState) -> Bool) {
sut.$state.sink { state in
guard condition(state) else { return }
expectation.fulfill()
}
.store(in: &cancellables)
sut.$state
.sink { state in
guard condition(state) else { return }
expectation.fulfill()
}
.store(in: &cancellables)
}
}
@@ -1010,7 +1011,7 @@ extension DraftAttachment {
size: 123456,
isListable: false
)
return DraftAttachment(state: state, attachment: mockAttachment, stateModifiedTimestamp: 1742829536)
return DraftAttachment(state: state, attachment: mockAttachment, stateModifiedTimestamp: 1_742_829_536)
}
}
@@ -1039,7 +1040,7 @@ private extension MockDraft {
size: 123456,
isListable: false
)
return [DraftAttachment(state: .uploaded, attachment: mockAttachment, stateModifiedTimestamp: 1742829536)]
return [DraftAttachment(state: .uploaded, attachment: mockAttachment, stateModifiedTimestamp: 1_742_829_536)]
}
static var defaultMockDraft: MockDraft {
@@ -43,7 +43,7 @@ final class DebouncedTaskTests: XCTestCase {
// MARK: Execute Immediately
func testExecuteImmediately_itShouldExecuteTheTask() async {
var result = [Int]()
var result: [Int] = []
sut = .init(duration: .seconds(10), block: { result.append(1) }, onBlockCompletion: {})
await sut.executeImmediately()
@@ -51,7 +51,7 @@ final class DebouncedTaskTests: XCTestCase {
}
func testExecuteImmediately_itShouldCancelTheDebouncedOpertionToAvoidRunningItTwice() async {
var result = [Int]()
var result: [Int] = []
let duration = Duration.milliseconds(10)
sut = .init(duration: duration, block: { result.append(1) }, onBlockCompletion: {})
@@ -63,7 +63,7 @@ final class DebouncedTaskTests: XCTestCase {
}
func testExecuteImmediately_itShouldCallOnBlockCompletion() async {
var result = [Int]()
var result: [Int] = []
var completionCalled = false
let duration = Duration.milliseconds(10)
sut = .init(
@@ -37,7 +37,7 @@ final class ScheduleSendDateFormatterTests {
.tomorrowTime
.date
}
private var distantFuture: Date { Date(timeIntervalSince1970: 1889427600) }
private var distantFuture: Date { Date(timeIntervalSince1970: 1_889_427_600) }
// MARK: Format.short
+1 -1
View File
@@ -22,7 +22,7 @@ public final class AppLogger: @unchecked Sendable {
private static let shared = AppLogger()
private let serialQueue = DispatchQueue(label: "\(Bundle.defaultIdentifier).AppLogger")
private var loggers = [String: Any]()
private var loggers: [String: Any] = [:]
private var bundleId: String {
Bundle.main.bundleIdentifier ?? Bundle.defaultIdentifier
}
@@ -35,6 +35,7 @@ public struct BlurredCoverView: View {
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
.background(.ultraThinMaterial)
}.ignoresSafeArea()
}
.ignoresSafeArea()
}
}
@@ -46,7 +46,8 @@ public struct LockScreen: View {
error: $store.state.pinAuthenticationError
) { output in
store.handle(action: .pin(output))
}.onLoad {
}
.onLoad {
store.handle(action: .pinScreenLoaded)
}
case .biometric:
@@ -121,7 +121,8 @@ public struct PINLockScreen: View {
.foregroundStyle(DS.Color.Text.weak)
.transition(.identity)
}
}.animation(.easeInOut(duration: 0.2), value: store.state.error)
}
.animation(.easeInOut(duration: 0.2), value: store.state.error)
}
private var pinBinding: Binding<String> {
@@ -63,8 +63,9 @@ private struct TransparentBlur: UIViewRepresentable {
private class TransparentBlurView: UIVisualEffectView {
override func layoutSublayers(of layer: CALayer) {
layer.sublayers?.first?.filters?.removeAll(where: { filter in
String(describing: filter) != "gaussianBlur"
})
layer.sublayers?.first?.filters?
.removeAll(where: { filter in
String(describing: filter) != "gaussianBlur"
})
}
}
+4 -4
View File
@@ -37,13 +37,13 @@ open class Keychain {
public var errorDescription: String? {
switch self {
case let .readFailed(key, code):
case .readFailed(let key, let code):
return "Keychain.AccessError.readFailed(\(key), \(code))"
case let .writeFailed(key, code):
case .writeFailed(let key, let code):
return "Keychain.AccessError.writeFailed(\(key), \(code))"
case let .updateFailed(key, code):
case .updateFailed(let key, let code):
return "Keychain.AccessError.updateFailed(\(key), \(code))"
case let .deleteFailed(key, code):
case .deleteFailed(let key, let code):
return "Keychain.AccessError.deleteFailed(\(key), \(code))"
}
}
@@ -60,7 +60,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.dataOrError(forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.readFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.readFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -73,7 +76,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.stringOrError(forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.readFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.readFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -96,7 +102,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.setOrError(Data(), forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.writeFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.writeFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -109,7 +118,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.setOrError(String(), forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.writeFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.writeFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -132,7 +144,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.setOrError(Data(), forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.updateFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.updateFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -145,7 +160,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.setOrError(String(), forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.updateFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.updateFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -168,7 +186,10 @@ final class KeychainTests: XCTestCase {
// when
XCTAssertThrowsError(try out.removeOrError(forKey: "any.key")) { error in
// then
guard case let Keychain.AccessError.deleteFailed(key, errorCode) = error else { XCTFail(); return }
guard case Keychain.AccessError.deleteFailed(let key, let errorCode) = error else {
XCTFail()
return
}
XCTAssertEqual(key, "any.key")
XCTAssertEqual(errorCode, errSecInteractionNotAllowed)
}
@@ -56,14 +56,15 @@ enum EventDateFormatter {
return relativeFormatter.string(from: relativeDate)
}
var allDayStyle = Date.FormatStyle(
date: .abbreviated,
time: .omitted,
locale: calendar.locale!,
calendar: calendar,
timeZone: calendar.timeZone
)
.weekday()
var allDayStyle =
Date.FormatStyle(
date: .abbreviated,
time: .omitted,
locale: calendar.locale!,
calendar: calendar,
timeZone: calendar.timeZone
)
.weekday()
if calendar.isDate(fromDate, equalTo: now, toGranularity: .year) {
if #available(iOS 18, *) {
@@ -74,25 +75,27 @@ enum EventDateFormatter {
return fromDate.formatted(allDayStyle)
}
let allDayIntervalStyle = Date.IntervalFormatStyle(
date: .abbreviated,
time: .omitted,
locale: calendar.locale!,
calendar: calendar,
timeZone: calendar.timeZone
)
.weekday()
let allDayIntervalStyle =
Date.IntervalFormatStyle(
date: .abbreviated,
time: .omitted,
locale: calendar.locale!,
calendar: calendar,
timeZone: calendar.timeZone
)
.weekday()
return allDayIntervalStyle.format(fromDate..<adjustedToDate)
case .dateTime:
let calendar = DateEnvironment.calendar
let dateTimeStyle = Date.IntervalFormatStyle(
date: .abbreviated,
time: .shortened,
locale: calendar.locale!,
timeZone: calendar.timeZone
)
.weekday()
let dateTimeStyle =
Date.IntervalFormatStyle(
date: .abbreviated,
time: .shortened,
locale: calendar.locale!,
timeZone: calendar.timeZone
)
.weekday()
return dateTimeStyle.format(fromDate..<toDate)
}
@@ -46,7 +46,7 @@ enum EventMapper {
private static func answerButtonsState(from state: RsvpState, attendeeIndex: UInt32?) -> Event.AnswerButtonsState {
let buttonsState: Event.AnswerButtonsState
if case let .answerableInvite(_, attendance) = state, let attendeeIndex {
if case .answerableInvite(_, let attendance) = state, let attendeeIndex {
buttonsState = .visible(attendance: attendance, attendeeIndex: Int(attendeeIndex))
} else {
buttonsState = .hidden
@@ -57,7 +57,7 @@ enum EventMapper {
private static func banner(from state: RsvpState) -> Event.Banner? {
switch state {
case let .answerableInvite(progress, _), let .reminder(progress):
case .answerableInvite(let progress, _), .reminder(let progress):
switch progress {
case .pending:
return nil
@@ -102,12 +102,13 @@ enum EventMapper {
}
private static func participants(attendees: [RsvpAttendee], userIndex: UInt32?) -> [Event.Participant] {
attendees.enumerated().map { index, attendee in
let isCurrentUser = isCurrentUser(attendeeIndex: index, userAttendeeIndex: userIndex)
let displayName = isCurrentUser ? userDisplayName(from: attendee) : otherAttendeeDisplayName(from: attendee)
attendees.enumerated()
.map { index, attendee in
let isCurrentUser = isCurrentUser(attendeeIndex: index, userAttendeeIndex: userIndex)
let displayName = isCurrentUser ? userDisplayName(from: attendee) : otherAttendeeDisplayName(from: attendee)
return .init(email: attendee.email, displayName: displayName, status: attendee.status)
}
return .init(email: attendee.email, displayName: displayName, status: attendee.status)
}
}
private static func isCurrentUser(attendeeIndex: Int, userAttendeeIndex: UInt32?) -> Bool {
@@ -69,11 +69,11 @@ final class RSVPStateStore: StateStore {
case .onLoad, .retry:
await loadEventDetails()
case .answer(let status):
if case let .loaded(service, event) = internalState {
if case .loaded(let service, let event) = internalState {
await answer(with: status, event: event, service: service)
}
case .calendarIconTapped:
if case let .loaded(_, event) = internalState {
if case .loaded(_, let event) = internalState {
tryToOpenEventInCalendarApp(with: event)
}
case .copyAddress(let email):
@@ -36,7 +36,7 @@ struct EventHeader: View {
.fontWeight(.medium)
.foregroundStyle(DS.Color.Text.norm)
.minimumScaleFactor(0.75)
if case let .visible(attendance, _) = answerButtons, attendance == .optional {
if case .visible(let attendance, _) = answerButtons, attendance == .optional {
Text(L10n.attendanceOptional)
.font(.footnote)
.fontWeight(.regular)
@@ -50,7 +50,7 @@ struct RSVPEventView: View {
VStack(alignment: .leading, spacing: DS.Spacing.large) {
eventHeader
.padding(.horizontal, DS.Spacing.extraLarge)
if case let .visible(_, userParticipantIndex) = event.answerButtons {
if case .visible(_, let userParticipantIndex) = event.answerButtons {
answerSection(userParticipantIndex: userParticipantIndex)
.padding(.bottom, DS.Spacing.small)
.padding(.horizontal, DS.Spacing.extraLarge)
@@ -165,8 +165,8 @@ private extension RsvpAttendeeStatus {
location: "Huddle Room",
description: "A brief check-in.",
recurrence: nil,
startsAt: 1754042400, // Aug 1, 2025 10:00 AM UTC
endsAt: 1754044200, // Aug 1, 2025 10:30 AM UTC
startsAt: 1_754_042_400, // Aug 1, 2025 10:00 AM UTC
endsAt: 1_754_044_200, // Aug 1, 2025 10:30 AM UTC
occurrence: .dateTime,
organizer: RsvpOrganizer(name: .none, email: "organizer1@example.com"),
attendees: [
@@ -26,8 +26,8 @@ import proton_app_uniffi
final class EventMapperTests {
@Test(
arguments: [
(summary: Optional<String>("Amazing Apple event!"), expected: "Amazing Apple event!"),
(summary: Optional<String>(nil), expected: L10n.noEventTitlePlacholder.string),
(summary: String?("Amazing Apple event!"), expected: "Amazing Apple event!"),
(summary: String?(nil), expected: L10n.noEventTitlePlacholder.string),
]
)
func testTitleMapping(summary: String?, expectedTitle: String) {
@@ -39,7 +39,7 @@ final class EventMapperTests {
@Test(
arguments: zip(
Array<RsvpEvent>([
[RsvpEvent]([
RsvpEvent.testData(
userAttendeeIdx: 1,
state: .answerableInvite(progress: .pending, attendance: .optional)
@@ -89,7 +89,7 @@ final class EventMapperTests {
RsvpState.cancelledReminder,
],
[
Optional<Event.Banner>(nil),
Event.Banner?(nil),
Event.Banner(style: .now, regularText: L10n.Header.happening, boldText: L10n.Header.now),
.init(style: .ended, regularText: L10n.Header.event, boldText: L10n.Header.ended),
nil,
@@ -134,8 +134,8 @@ final class EventMapperTests {
@Test(arguments: [
(
userAttendeeIndex: Optional<UInt32>(1),
expected: Array<Event.Participant>([
userAttendeeIndex: UInt32?(1),
expected: [Event.Participant]([
.init(email: "alice@proton.me", displayName: "Alice Sherington • alice@proton.me", status: .yes),
.init(email: "bob@outlook.com", displayName: "You • bob@outlook.com", status: .no, ),
.init(email: "cyril@gmail.com", displayName: "cyril@gmail.com", status: .maybe, ),
@@ -144,7 +144,7 @@ final class EventMapperTests {
),
(
userAttendeeIndex: Optional<UInt32>.none,
expected: Array<Event.Participant>([
expected: [Event.Participant]([
.init(email: "alice@proton.me", displayName: "Alice Sherington • alice@proton.me", status: .yes),
.init(email: "bob@outlook.com", displayName: "Bob Charlton • bob@outlook.com", status: .no),
.init(email: "cyril@gmail.com", displayName: "cyril@gmail.com", status: .maybe, ),
@@ -228,7 +228,7 @@ final class RSVPStateStoreTests {
func calendarIconTappedAction_WhenHasEventInformation_ItOpensCalendarAppWithEventDetails() async {
let expectedEvent: RsvpEvent = .bestEvent(
id: "event_id_9",
startsAt: 1672531200,
startsAt: 1_672_531_200,
calendar: .init(id: "calendar_id_42", name: "Work", color: .empty)
)
@@ -252,7 +252,7 @@ final class RSVPStateStoreTests {
func calendarIconTappedAction_WhenHasEventInformationButAppVersionIsToOld_ItTriesToOpenCalendarAppAndFallbacksToAppStore() async {
let expectedEvent: RsvpEvent = .bestEvent(
id: "event_id_3",
startsAt: 1609459200,
startsAt: 1_609_459_200,
calendar: .init(id: "calendar_id_19", name: "Work", color: .empty)
)
@@ -31,7 +31,7 @@ extension Conversation {
displaySnoozeReminder: false,
snoozedUntil: nil,
locations: [.system(name: .inbox, id: .init(value: 41))],
expirationTime: 1625140800,
expirationTime: 1_625_140_800,
isStarred: true,
numAttachments: 0,
numMessages: 1,
@@ -43,7 +43,7 @@ extension Conversation {
senders: senders,
size: 1_024,
subject: .notUsed,
time: 1622548800,
time: 1_622_548_800,
avatar: .init(text: .notUsed, color: .notUsed),
hiddenMessagesBanner: nil
)
@@ -34,7 +34,7 @@ extension Message {
bccList: bcc,
ccList: cc,
location: .system(name: .inbox, id: .init(value: 33)),
expirationTime: 1625140800,
expirationTime: 1_625_140_800,
flags: .init(value: 2),
isForwarded: true,
isReplied: true,
@@ -46,7 +46,7 @@ extension Message {
snoozedUntil: .none,
displaySnoozeReminder: false,
subject: .notUsed,
time: 1622548800,
time: 1_622_548_800,
toList: to,
unread: true,
customLabels: [],
@@ -46,18 +46,19 @@ final class ShareViewController: UINavigationController {
}
private func setUpBindings(observing model: ShareScreenModel) {
model.$alert.sink { [weak self] message in
guard let self else { return }
model.$alert
.sink { [weak self] message in
guard let self else { return }
if presentedViewController != nil {
dismiss(animated: true)
}
if presentedViewController != nil {
dismiss(animated: true)
}
if let message {
let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
present(alert, animated: true)
if let message {
let alert = UIAlertController(title: message, message: nil, preferredStyle: .alert)
present(alert, animated: true)
}
}
}
.store(in: &cancellables)
.store(in: &cancellables)
}
}
@@ -74,9 +74,10 @@ final class DraftStubWriterTests {
@Test
func movesNonInlineAttachmentsToTemporaryDirectoryBeforeAdding() async throws {
let sourceURLs = (0..<3).map { index in
attachmentSourceDir.appending(path: "data-\(index).txt")
}
let sourceURLs = (0..<3)
.map { index in
attachmentSourceDir.appending(path: "data-\(index).txt")
}
let sharedContent = SharedContent(
subject: nil,
@@ -100,9 +101,10 @@ final class DraftStubWriterTests {
@Test
func movesInlineAttachmentsToTemporaryDirectoryBeforeAdding() async throws {
let sourceURLs = (0..<3).map { index in
attachmentSourceDir.appending(path: "image-\(index).png")
}
let sourceURLs = (0..<3)
.map { index in
attachmentSourceDir.appending(path: "image-\(index).png")
}
let sharedContent = SharedContent(
subject: nil,
@@ -126,9 +128,10 @@ final class DraftStubWriterTests {
@Test
func extractsImagesFromScreenshotPlists() async throws {
let sourceURLs = (0..<3).map { index in
attachmentSourceDir.appending(path: "image-\(index).png")
}
let sourceURLs = (0..<3)
.map { index in
attachmentSourceDir.appending(path: "image-\(index).png")
}
let sharedContent = SharedContent(
subject: nil,
@@ -111,10 +111,11 @@ final class SharedItemsParserTests {
]
if fromWithinPage {
extensionItem.attachments!.insert(
.init(item: "irrelevant" as NSSecureCoding, typeIdentifier: UTType.plainText.identifier),
at: 0
)
extensionItem.attachments!
.insert(
.init(item: "irrelevant" as NSSecureCoding, typeIdentifier: UTType.plainText.identifier),
at: 0
)
}
return [extensionItem]
@@ -21,20 +21,21 @@ import UniformTypeIdentifiers
enum TestDataFactory {
static func makeItemProviders(types: [UTType], count: UInt) -> [NSItemProvider] {
(0..<count).map { index in
let itemProvider = NSItemProvider()
(0..<count)
.map { index in
let itemProvider = NSItemProvider()
for type in types {
let url = URL(fileURLWithPath: "attachments/\(index)-\(type.identifier)")
for type in types {
let url = URL(fileURLWithPath: "attachments/\(index)-\(type.identifier)")
itemProvider.registerFileRepresentation(for: type) { completion in
completion(url, true, nil)
return nil
itemProvider.registerFileRepresentation(for: type) { completion in
completion(url, true, nil)
return nil
}
}
}
return itemProvider
}
return itemProvider
}
}
static func stubShortLivedData(in urls: [URL]) throws -> [NSItemProvider] {