mirror of
https://github.com/ProtonDriveApps/mac-drive.git
synced 2026-05-15 09:50:33 +00:00
2.10.0
This commit is contained in:
@@ -75,4 +75,13 @@ public enum ExternalFeatureFlag: String, CaseIterable, Codable {
|
||||
|
||||
// Payments
|
||||
case driveiOSPaymentsV2 = "DriveiOSPaymentsV2"
|
||||
|
||||
// SDK
|
||||
case driveiOSSDKUploadMain = "DriveiOSSDKUploadMain"
|
||||
case driveiOSSDKUploadPhoto = "DriveiOSSDKUploadPhoto"
|
||||
case driveiOSSDKDownloadMain = "DriveiOSSDKDownloadMain"
|
||||
case driveiOSSDKDownloadPhoto = "DriveiOSSDKDownloadPhoto"
|
||||
|
||||
// Black Friday 2025
|
||||
case driveIOSBlackFriday2025 = "DriveIOSBlackFriday2025"
|
||||
}
|
||||
|
||||
@@ -38,21 +38,44 @@ public class EventStorageManager: NSObject, RecoverableStorage {
|
||||
return model
|
||||
}
|
||||
|
||||
#if RESOURCES_ARE_IMPORTED_BY_SPM
|
||||
#if RESOURCES_ARE_IMPORTED_BY_SPM && !canImport(XCTest)
|
||||
if let bundle = Bundle.module.url(forResource: databaseName, withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: bundle)
|
||||
{
|
||||
return model
|
||||
}
|
||||
#elseif RESOURCES_ARE_IMPORTED_BY_SPM && canImport(XCTest)
|
||||
// Find the model manually in case we're running tests.
|
||||
if let libraryPath = ProcessInfo.processInfo.environment["DYLD_LIBRARY_PATH"]?.split(separator: ":").first,
|
||||
let resourceBundle = Bundle(path: libraryPath + "/PDCore_PDCore.bundle"),
|
||||
let modelURL = resourceBundle.url(forResource: databaseName, withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: modelURL)
|
||||
{
|
||||
return model
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
// dynamic linking
|
||||
if let bundle = Bundle(for: EventStorageManager.self).url(forResource: databaseName, withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: bundle)
|
||||
{
|
||||
return model
|
||||
}
|
||||
|
||||
|
||||
// Debug builds for real devices link XCTest in, causing problems when developing
|
||||
// on an iOS device. This doesn't happen for macOS.
|
||||
//
|
||||
// We work around this by trying the SPM/application code path even in case we
|
||||
// already tried looking for resources in DYLD_LIBRARY_PATH.
|
||||
//
|
||||
// We shouldn't remove the compile-time checks because checking Bundle.module while
|
||||
// running macOS tests will crash as the resources aren't where it expects.
|
||||
if let url = Bundle.module.url(forResource: databaseName, withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: url) {
|
||||
return model
|
||||
}
|
||||
|
||||
fatalError("Error loading EventStorageModel from bundle")
|
||||
}()
|
||||
|
||||
|
||||
@@ -187,6 +187,14 @@ class ExternalFeatureFlagsRepository: FeatureFlagsRepository {
|
||||
case .driveiOSDebugMode: return .driveiOSDebugMode
|
||||
// Payments
|
||||
case .driveiOSPaymentsV2: return .driveiOSPaymentsV2
|
||||
// SDK
|
||||
case .driveiOSSDKUploadMain: return .driveiOSSDKUploadMain
|
||||
case .driveiOSSDKUploadPhoto: return .driveiOSSDKUploadPhoto
|
||||
case .driveiOSSDKDownloadMain: return .driveiOSSDKDownloadMain
|
||||
case .driveiOSSDKDownloadPhoto: return .driveiOSSDKDownloadPhoto
|
||||
|
||||
// Black Friday 2025
|
||||
case .driveIOSBlackFriday2025: return .driveIOSBlackFriday2025
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ extension LocalSettings: ExternalFeatureFlagsStore {
|
||||
case .driveiOSDebugMode: driveiOSDebugMode = value
|
||||
// Payments
|
||||
case .driveiOSPaymentsV2: driveiOSPaymentsV2 = value
|
||||
// SDK
|
||||
case .driveiOSSDKUploadMain: driveiOSSDKUploadMain = value
|
||||
case .driveiOSSDKUploadPhoto: driveiOSSDKUploadPhoto = value
|
||||
case .driveiOSSDKDownloadMain: driveiOSSDKDownloadMain = value
|
||||
case .driveiOSSDKDownloadPhoto: driveiOSSDKDownloadPhoto = value
|
||||
// Black Friday 2025
|
||||
case .driveIOSBlackFriday2025: driveIOSBlackFriday2025 = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +140,13 @@ extension LocalSettings: ExternalFeatureFlagsStore {
|
||||
case .driveiOSDebugMode: return driveiOSDebugMode
|
||||
// Payments
|
||||
case .driveiOSPaymentsV2: return driveiOSPaymentsV2
|
||||
// SDK
|
||||
case .driveiOSSDKUploadMain: return driveiOSSDKUploadMain
|
||||
case .driveiOSSDKUploadPhoto: return driveiOSSDKUploadPhoto
|
||||
case .driveiOSSDKDownloadMain: return driveiOSSDKDownloadMain
|
||||
case .driveiOSSDKDownloadPhoto: return driveiOSSDKDownloadPhoto
|
||||
// Black Friday 2025
|
||||
case .driveIOSBlackFriday2025: return driveIOSBlackFriday2025
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,4 +77,13 @@ public enum FeatureAvailabilityFlag: CaseIterable {
|
||||
|
||||
// Payments
|
||||
case driveiOSPaymentsV2
|
||||
|
||||
// SDK
|
||||
case driveiOSSDKUploadMain
|
||||
case driveiOSSDKUploadPhoto
|
||||
case driveiOSSDKDownloadMain
|
||||
case driveiOSSDKDownloadPhoto
|
||||
|
||||
// Black Friday 2025
|
||||
case driveIOSBlackFriday2025
|
||||
}
|
||||
|
||||
@@ -34,12 +34,22 @@ extension NSManagedObjectModel {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if RESOURCES_ARE_IMPORTED_BY_SPM
|
||||
#if RESOURCES_ARE_IMPORTED_BY_SPM && !canImport(XCTest)
|
||||
// Looking for the bundle via Bundle.module works fine for SPM packages linked to applications.
|
||||
if let bundle = Bundle.module.url(forResource: "Metadata", withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: bundle)
|
||||
{
|
||||
return model
|
||||
}
|
||||
#elseif RESOURCES_ARE_IMPORTED_BY_SPM && canImport(XCTest)
|
||||
// But we need to do it manually for tests.
|
||||
if let libraryPath = ProcessInfo.processInfo.environment["DYLD_LIBRARY_PATH"]?.split(separator: ":").first,
|
||||
let resourceBundle = Bundle(path: libraryPath + "/PDCore_PDCore.bundle"),
|
||||
let modelURL = resourceBundle.url(forResource: "Metadata", withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: modelURL)
|
||||
{
|
||||
return model
|
||||
}
|
||||
#endif
|
||||
|
||||
// dynamic linking
|
||||
@@ -49,6 +59,19 @@ extension NSManagedObjectModel {
|
||||
return model
|
||||
}
|
||||
|
||||
// Debug builds for real devices link XCTest in, causing problems when developing
|
||||
// on an iOS device. This doesn't happen for macOS.
|
||||
//
|
||||
// We work around this by trying the SPM/application code path even in case we
|
||||
// already tried looking for it in DYLD_LIBRARY_PATH.
|
||||
//
|
||||
// We shouldn't remove the compile-time checks because checking Bundle.module while
|
||||
// running macOS tests will crash as the resources aren't where it expects.
|
||||
if let url = Bundle.module.url(forResource: "Metadata", withExtension: "momd"),
|
||||
let model = NSManagedObjectModel(contentsOf: url) {
|
||||
return model
|
||||
}
|
||||
|
||||
fatalError("Error loading Metadata from bundle")
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ final class AsyncThumbnailLoader: CancellableThumbnailLoader {
|
||||
extension AsyncThumbnailLoader {
|
||||
func loadThumbnail(with id: Identifier) {
|
||||
guard isIdAllowed(id) else {
|
||||
Log.info("Load thumbnail not allowed: \(id)", domain: .thumbnails)
|
||||
Log.debug("Load thumbnail not allowed: \(id)", domain: .thumbnails)
|
||||
failedIdSubject.send(id)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
import ProtonCoreNetworking
|
||||
import ProtonCoreServices
|
||||
@@ -23,18 +24,16 @@ import PMEventsManager
|
||||
public typealias UserSettings = PMEventsManager.UserSettings
|
||||
|
||||
public final class GeneralSettings {
|
||||
@SecureStorage(label: "userSettings") private(set) var userSettings: UserSettings?
|
||||
@SecureStorage(label: "userSettings") public private(set) var currentUserSettings: UserSettings?
|
||||
public private(set) var userSettings: CurrentValueSubject<UserSettings?, Never> = .init(nil)
|
||||
|
||||
private let network: ProtonCoreServices.APIService
|
||||
private let localSettings: LocalSettings
|
||||
|
||||
|
||||
init(mainKeyProvider: MainKeyProvider, network: ProtonCoreServices.APIService, localSettings: LocalSettings) {
|
||||
self.network = network
|
||||
self.localSettings = localSettings
|
||||
self._userSettings.configure(with: mainKeyProvider)
|
||||
}
|
||||
|
||||
public var currentUserSettings: UserSettings? {
|
||||
userSettings
|
||||
self._currentUserSettings.configure(with: mainKeyProvider)
|
||||
}
|
||||
|
||||
public func fetchUserSettings() {
|
||||
@@ -65,14 +64,16 @@ public final class GeneralSettings {
|
||||
}
|
||||
|
||||
public func storeUserSettings(_ userSettings: UserSettings) {
|
||||
self.userSettings = userSettings
|
||||
|
||||
self.currentUserSettings = userSettings
|
||||
|
||||
self.localSettings.optOutFromTelemetry = userSettings.optOutFromTelementry
|
||||
self.localSettings.optOutFromCrashReports = userSettings.optOutFromCrashReports
|
||||
|
||||
self.userSettings.send(userSettings)
|
||||
}
|
||||
|
||||
public func cleanUp() {
|
||||
try? _userSettings.wipeValue()
|
||||
try? _currentUserSettings.wipeValue()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -132,6 +132,15 @@ public class LocalSettings: NSObject {
|
||||
@SettingsStorage("didFetchProtonUserSettings") public var didFetchProtonUserSettings: Bool?
|
||||
@SettingsStorage("didFetchB2BStatus") public var didFetchB2BStatus: Bool?
|
||||
|
||||
// SDK FF
|
||||
@SettingsStorage("DriveiOSSDKUploadMainValue") private var driveiOSSDKUploadMainValue: Bool?
|
||||
@SettingsStorage("DriveiOSSDKUploadPhotoValue") private var driveiOSSDKUploadPhotoValue: Bool?
|
||||
@SettingsStorage("DriveiOSSDKDownloadMainValue") private var driveiOSSDKDownloadMainValue: Bool?
|
||||
@SettingsStorage("DriveiOSSDKDownloadPhotoValue") private var driveiOSSDKDownloadPhotoValue: Bool?
|
||||
|
||||
// Black Friday 2025
|
||||
@SettingsStorage("driveIOSBlackFriday2025") private var driveIOSBlackFriday2025Value: Bool?
|
||||
|
||||
public let suite: SettingsStorageSuite
|
||||
|
||||
public init(suite: SettingsStorageSuite) {
|
||||
@@ -243,6 +252,13 @@ public class LocalSettings: NSObject {
|
||||
self._didFetchDriveUserSettings.configure(with: suite)
|
||||
self._didFetchProtonUserSettings.configure(with: suite)
|
||||
self._didFetchB2BStatus.configure(with: suite)
|
||||
// SDK
|
||||
self._driveiOSSDKUploadMainValue.configure(with: suite)
|
||||
self._driveiOSSDKUploadPhotoValue.configure(with: suite)
|
||||
self._driveiOSSDKDownloadMainValue.configure(with: suite)
|
||||
self._driveiOSSDKDownloadPhotoValue.configure(with: suite)
|
||||
// Black Friday 2025
|
||||
self._driveIOSBlackFriday2025Value.configure(with: suite)
|
||||
setDynamicVariables()
|
||||
}
|
||||
|
||||
@@ -303,6 +319,13 @@ public class LocalSettings: NSObject {
|
||||
self.driveSettingsDocsCommentsNotificationsEnabled = docsCommentsNotificationsEnabled ?? false
|
||||
self.driveSettingsDocsCommentsNotificationsIncludeDocumentName = docsCommentsNotificationsIncludeDocumentName ?? false
|
||||
self.driveSettingsPhotoTags = photoTags ?? [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
// SDK
|
||||
driveiOSSDKUploadMain = driveiOSSDKUploadMainValue ?? false
|
||||
driveiOSSDKUploadPhoto = driveiOSSDKUploadPhotoValue ?? false
|
||||
driveiOSSDKDownloadMain = driveiOSSDKDownloadMainValue ?? false
|
||||
driveiOSSDKDownloadPhoto = driveiOSSDKDownloadPhotoValue ?? false
|
||||
// Black Friday 2025
|
||||
driveIOSBlackFriday2025 = driveIOSBlackFriday2025Value ?? false
|
||||
}
|
||||
|
||||
/// `cleanUserSpecificSettings`
|
||||
@@ -389,9 +412,15 @@ public class LocalSettings: NSObject {
|
||||
self.docsCommentsNotificationsEnabled = nil
|
||||
self.docsCommentsNotificationsIncludeDocumentName = nil
|
||||
self.photoTags = nil
|
||||
|
||||
self.didFetchDriveUserSettings = nil
|
||||
self.didFetchProtonUserSettings = nil
|
||||
// SDK
|
||||
driveiOSSDKUploadMainValue = nil
|
||||
driveiOSSDKUploadPhotoValue = nil
|
||||
driveiOSSDKDownloadMainValue = nil
|
||||
driveiOSSDKDownloadPhotoValue = nil
|
||||
// Black Friday 2025
|
||||
driveIOSBlackFriday2025Value = nil
|
||||
setDynamicVariables()
|
||||
}
|
||||
|
||||
@@ -753,6 +782,31 @@ public class LocalSettings: NSObject {
|
||||
get { photoTags ?? [] }
|
||||
set { photoTags = newValue }
|
||||
}
|
||||
|
||||
public var driveiOSSDKUploadMain: Bool {
|
||||
get { driveiOSSDKUploadMainValue ?? false }
|
||||
set { driveiOSSDKUploadMainValue = newValue }
|
||||
}
|
||||
|
||||
public var driveiOSSDKUploadPhoto: Bool {
|
||||
get { driveiOSSDKUploadPhotoValue ?? false }
|
||||
set { driveiOSSDKUploadPhotoValue = newValue }
|
||||
}
|
||||
|
||||
public var driveiOSSDKDownloadMain: Bool {
|
||||
get { driveiOSSDKDownloadMainValue ?? false }
|
||||
set { driveiOSSDKDownloadMainValue = newValue }
|
||||
}
|
||||
|
||||
public var driveiOSSDKDownloadPhoto: Bool {
|
||||
get { driveiOSSDKDownloadPhotoValue ?? false }
|
||||
set { driveiOSSDKDownloadPhotoValue = newValue }
|
||||
}
|
||||
|
||||
@objc public dynamic var driveIOSBlackFriday2025: Bool {
|
||||
get { driveIOSBlackFriday2025Value ?? false }
|
||||
set { driveIOSBlackFriday2025Value = newValue }
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
@@ -43,6 +43,11 @@ public protocol FeatureFlagsControllerProtocol {
|
||||
var hasProtonSheetCreation: Bool { get }
|
||||
var hasDebugMode: Bool { get }
|
||||
var hasPaymentsV2: Bool { get }
|
||||
var hasSDKUploadMain: Bool { get }
|
||||
var hasSDKUploadPhoto: Bool { get }
|
||||
var hasSDKDownloadMain: Bool { get }
|
||||
var hasSDKDownloadPhoto: Bool { get }
|
||||
var hasIOSBlackFriday2025: Bool { get }
|
||||
/// Makes current value publisher for the specific FF
|
||||
func makePublisher(keyPath: KeyPath<FeatureFlagsControllerProtocol, Bool>) -> AnyPublisher<Bool, Never>
|
||||
}
|
||||
@@ -156,6 +161,26 @@ public final class FeatureFlagsController: FeatureFlagsControllerProtocol {
|
||||
return featureFlagsStore.isFeatureEnabled(.driveiOSPaymentsV2)
|
||||
}
|
||||
|
||||
public var hasSDKUploadMain: Bool {
|
||||
return buildType.isQaOrBelow && featureFlagsStore.isFeatureEnabled(.driveiOSSDKUploadMain)
|
||||
}
|
||||
|
||||
public var hasSDKUploadPhoto: Bool {
|
||||
return buildType.isQaOrBelow && featureFlagsStore.isFeatureEnabled(.driveiOSSDKUploadPhoto)
|
||||
}
|
||||
|
||||
public var hasSDKDownloadMain: Bool {
|
||||
return buildType.isQaOrBelow && featureFlagsStore.isFeatureEnabled(.driveiOSSDKDownloadMain)
|
||||
}
|
||||
|
||||
public var hasSDKDownloadPhoto: Bool {
|
||||
return buildType.isQaOrBelow && featureFlagsStore.isFeatureEnabled(.driveiOSSDKDownloadPhoto)
|
||||
}
|
||||
|
||||
public var hasIOSBlackFriday2025: Bool {
|
||||
featureFlagsStore.isFeatureEnabled(.driveIOSBlackFriday2025)
|
||||
}
|
||||
|
||||
public func makePublisher(keyPath: KeyPath<FeatureFlagsControllerProtocol, Bool>) -> AnyPublisher<Bool, Never> {
|
||||
let currentValue = self[keyPath: keyPath]
|
||||
let updatePublisher = updatePublisher
|
||||
|
||||
@@ -437,6 +437,12 @@
|
||||
"value" : "Resynchronisation complète"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Teljes újraszinkronizálás"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
|
||||
@@ -45857,6 +45857,12 @@
|
||||
"value" : "Accueil"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Kezdőlap"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
@@ -47854,6 +47860,12 @@
|
||||
"value" : "Nom du document"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dokumentum neve"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
@@ -47863,7 +47875,7 @@
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "文書の名"
|
||||
"value" : "文書の名前"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
@@ -47998,6 +48010,12 @@
|
||||
"value" : "Nommer votre scan"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Beolvasás elnevezése"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
@@ -78344,6 +78362,12 @@
|
||||
"value" : "Numériser un document"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Dokumentum szkennelése"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
@@ -112873,6 +112897,12 @@
|
||||
"value" : "Faites glisser vers le bas pour actualiser"
|
||||
}
|
||||
},
|
||||
"hu" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Frissítéshez húzza le"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
|
||||
@@ -73,22 +73,36 @@
|
||||
66E62A752D7B1865008222AC /* FullResyncCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E62A732D7B185F008222AC /* FullResyncCoordinator.swift */; };
|
||||
66E62A762D7B1865008222AC /* FullResyncCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66E62A732D7B185F008222AC /* FullResyncCoordinator.swift */; };
|
||||
66FAB3332AD55EF000ADAB83 /* LaunchOnBootService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66FAB3322AD55EF000ADAB83 /* LaunchOnBootService.swift */; };
|
||||
721D5AFC2E819C8000740E68 /* DBMeasurementCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9662E8146500013826F /* DBMeasurementCollector.swift */; };
|
||||
7201DEB42E9405D0009FEC92 /* PerformanceMetricsReportAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7201DEB32E9405C9009FEC92 /* PerformanceMetricsReportAggregator.swift */; };
|
||||
7201DEB52E9405D0009FEC92 /* PerformanceMetricsReportAggregator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7201DEB32E9405C9009FEC92 /* PerformanceMetricsReportAggregator.swift */; };
|
||||
721D72862E8A7C3000E45B33 /* FetchedResultObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = A3D7ED9B2CEF757A00A2FAB1 /* FetchedResultObserver.swift */; };
|
||||
7240FEAA2EA7747000B47EA7 /* PromoCampaignDateResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7240FEA92EA7746B00B47EA7 /* PromoCampaignDateResource.swift */; };
|
||||
7240FEAB2EA7747000B47EA7 /* PromoCampaignDateResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7240FEA92EA7746B00B47EA7 /* PromoCampaignDateResource.swift */; };
|
||||
726691C12E9FCACD00796513 /* PDCoreTestingToolkit in Frameworks */ = {isa = PBXBuildFile; productRef = 726691C02E9FCACD00796513 /* PDCoreTestingToolkit */; };
|
||||
726A96822EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96812EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift */; };
|
||||
726A96832EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96812EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift */; };
|
||||
726A96842EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96812EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift */; };
|
||||
726A96862EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96852EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift */; };
|
||||
726A96872EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96852EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift */; };
|
||||
726A96882EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 726A96852EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift */; };
|
||||
72ADD1892E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72ADD1882E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift */; };
|
||||
72ADD18A2E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72ADD1882E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift */; };
|
||||
72ADD18B2E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72ADD1882E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift */; };
|
||||
72ADD1902E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72ADD18E2E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift */; };
|
||||
72ADD1912E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72ADD18E2E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift */; };
|
||||
72FFB96A2E8146760013826F /* MeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9692E8146730013826F /* MeasurementEvent.swift */; };
|
||||
72FFB96B2E8146760013826F /* MeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9692E8146730013826F /* MeasurementEvent.swift */; };
|
||||
72BDED562EA0EF1500EC3302 /* PromoCampaignBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED552EA0EF0900EC3302 /* PromoCampaignBanner.swift */; };
|
||||
72BDED572EA0EF1500EC3302 /* PromoCampaignBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED552EA0EF0900EC3302 /* PromoCampaignBanner.swift */; };
|
||||
72BDED5A2EA0F53F00EC3302 /* PromoCampaignInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED592EA0F53900EC3302 /* PromoCampaignInteractor.swift */; };
|
||||
72BDED5B2EA0F53F00EC3302 /* PromoCampaignInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED592EA0F53900EC3302 /* PromoCampaignInteractor.swift */; };
|
||||
72BDED5D2EA0F6CC00EC3302 /* UserDefaults+PromoCampaigns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED5C2EA0F6C300EC3302 /* UserDefaults+PromoCampaigns.swift */; };
|
||||
72BDED5E2EA0F6CC00EC3302 /* UserDefaults+PromoCampaigns.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72BDED5C2EA0F6C300EC3302 /* UserDefaults+PromoCampaigns.swift */; };
|
||||
72C94D122E9D51FA00FA0038 /* PDCoreTestingToolkit in Frameworks */ = {isa = PBXBuildFile; productRef = 72C94D112E9D51FA00FA0038 /* PDCoreTestingToolkit */; };
|
||||
72FFB9862E8179820013826F /* Metrics.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9842E8179820013826F /* Metrics.xcdatamodeld */; };
|
||||
72FFB9872E8179820013826F /* Metrics.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9842E8179820013826F /* Metrics.xcdatamodeld */; };
|
||||
72FFB9882E8179820013826F /* Metrics.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9842E8179820013826F /* Metrics.xcdatamodeld */; };
|
||||
72FFB98A2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9892E8179D10013826F /* DBPerformanceMeasurement.swift */; };
|
||||
72FFB98B2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9892E8179D10013826F /* DBPerformanceMeasurement.swift */; };
|
||||
72FFB98C2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9892E8179D10013826F /* DBPerformanceMeasurement.swift */; };
|
||||
72FFB98D2E817CAD0013826F /* MeasurementEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 72FFB9692E8146730013826F /* MeasurementEvent.swift */; };
|
||||
76A2BBDA2AE25F26005B77A2 /* NotificationName+Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76A2BBD92AE25F26005B77A2 /* NotificationName+Window.swift */; };
|
||||
76CEAF432A6CF67A000586F4 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76CEAF422A6CF67A000586F4 /* OnboardingView.swift */; };
|
||||
76CEAF452A6CF695000586F4 /* OnboardingCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76CEAF442A6CF695000586F4 /* OnboardingCoordinator.swift */; };
|
||||
@@ -254,6 +268,13 @@
|
||||
remoteGlobalIDString = 6690C7FA2AD49DD60005FC8F;
|
||||
remoteInfo = ProtonDriveMacLauncher;
|
||||
};
|
||||
7240FEAE2EA7927C00B47EA7 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = AB71531824274ED900543720 /* Project object */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 6690C7FA2AD49DD60005FC8F;
|
||||
remoteInfo = ProtonDriveMacLauncher;
|
||||
};
|
||||
D4040203282018B9001D465B /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = AB71531824274ED900543720 /* Project object */;
|
||||
@@ -372,10 +393,15 @@
|
||||
66E62A732D7B185F008222AC /* FullResyncCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullResyncCoordinator.swift; sourceTree = "<group>"; };
|
||||
66F061982B850C5600A6B067 /* stress-tests */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = "stress-tests"; sourceTree = "<group>"; };
|
||||
66FAB3322AD55EF000ADAB83 /* LaunchOnBootService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchOnBootService.swift; sourceTree = "<group>"; };
|
||||
7201DEB32E9405C9009FEC92 /* PerformanceMetricsReportAggregator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceMetricsReportAggregator.swift; sourceTree = "<group>"; };
|
||||
7240FEA92EA7746B00B47EA7 /* PromoCampaignDateResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromoCampaignDateResource.swift; sourceTree = "<group>"; };
|
||||
726A96812EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PerformanceMeasurementEvent.swift; sourceTree = "<group>"; };
|
||||
726A96852EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPerformanceMeasurementCollector.swift; sourceTree = "<group>"; };
|
||||
72ADD1882E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPerformanceMeasurementRepository.swift; sourceTree = "<group>"; };
|
||||
72ADD18E2E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPerformanceMetricsReporter.swift; sourceTree = "<group>"; };
|
||||
72FFB9662E8146500013826F /* DBMeasurementCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBMeasurementCollector.swift; sourceTree = "<group>"; };
|
||||
72FFB9692E8146730013826F /* MeasurementEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeasurementEvent.swift; sourceTree = "<group>"; };
|
||||
72BDED552EA0EF0900EC3302 /* PromoCampaignBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromoCampaignBanner.swift; sourceTree = "<group>"; };
|
||||
72BDED592EA0F53900EC3302 /* PromoCampaignInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromoCampaignInteractor.swift; sourceTree = "<group>"; };
|
||||
72BDED5C2EA0F6C300EC3302 /* UserDefaults+PromoCampaigns.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+PromoCampaigns.swift"; sourceTree = "<group>"; };
|
||||
72FFB9852E8179820013826F /* Metrics.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Metrics.xcdatamodel; sourceTree = "<group>"; };
|
||||
72FFB9892E8179D10013826F /* DBPerformanceMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBPerformanceMeasurement.swift; sourceTree = "<group>"; };
|
||||
763AA3F129374F9F00AEE68E /* Config-Release-Store.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Config-Release-Store.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -578,16 +604,27 @@
|
||||
72ADD18F2E8FBE0700A7ABB3 /* Reporting */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7201DEB32E9405C9009FEC92 /* PerformanceMetricsReportAggregator.swift */,
|
||||
72ADD18E2E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift */,
|
||||
);
|
||||
path = Reporting;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
72BDED582EA0F51E00EC3302 /* PromoCampaigns */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7240FEA92EA7746B00B47EA7 /* PromoCampaignDateResource.swift */,
|
||||
72BDED5C2EA0F6C300EC3302 /* UserDefaults+PromoCampaigns.swift */,
|
||||
72BDED592EA0F53900EC3302 /* PromoCampaignInteractor.swift */,
|
||||
);
|
||||
path = PromoCampaigns;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
72FFB9602E8140430013826F /* Metrics */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
726A96812EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift */,
|
||||
72FFB9832E8179690013826F /* DB */,
|
||||
72FFB9692E8146730013826F /* MeasurementEvent.swift */,
|
||||
72FFB9722E814AED0013826F /* Storage */,
|
||||
72FFB9642E8146420013826F /* Collection */,
|
||||
);
|
||||
@@ -597,7 +634,7 @@
|
||||
72FFB9642E8146420013826F /* Collection */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
72FFB9662E8146500013826F /* DBMeasurementCollector.swift */,
|
||||
726A96852EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift */,
|
||||
);
|
||||
path = Collection;
|
||||
sourceTree = "<group>";
|
||||
@@ -657,6 +694,7 @@
|
||||
A30308ED2CE74CC400087F2E /* Subviews */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
72BDED552EA0EF0900EC3302 /* PromoCampaignBanner.swift */,
|
||||
D744EE452E059A9F008B005A /* SpinningImage.swift */,
|
||||
A30308E72CE74CC400087F2E /* FooterView.swift */,
|
||||
A30308E82CE74CC400087F2E /* HeaderView.swift */,
|
||||
@@ -792,6 +830,7 @@
|
||||
AB71532224274ED900543720 /* ProtonDriveMac */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
72BDED582EA0F51E00EC3302 /* PromoCampaigns */,
|
||||
72ADD18F2E8FBE0700A7ABB3 /* Reporting */,
|
||||
A3D773452D3AA84400769A8D /* TestRunner.swift */,
|
||||
D4CC491828705C7F00C6E83F /* main.m */,
|
||||
@@ -1113,14 +1152,14 @@
|
||||
);
|
||||
mainGroup = AB71531724274ED900543720;
|
||||
packageReferences = (
|
||||
D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle" */,
|
||||
D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle.git" */,
|
||||
D8AB30D62C6217B5006A5F7C /* XCRemoteSwiftPackageReference "OHHTTPStubs" */,
|
||||
D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit" */,
|
||||
D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion" */,
|
||||
D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit.git" */,
|
||||
D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion.git" */,
|
||||
3E9137BE2CC77C0400651BC1 /* XCRemoteSwiftPackageReference "SwiftLintPlugins" */,
|
||||
661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift" */,
|
||||
66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams" */,
|
||||
66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */,
|
||||
661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift.git" */,
|
||||
66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams.git" */,
|
||||
66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */,
|
||||
);
|
||||
productRefGroup = AB71532124274ED900543720 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -1261,8 +1300,10 @@
|
||||
669776DC2AF15BE700929E9A /* AppDelegate.swift in Sources */,
|
||||
72FFB98C2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */,
|
||||
669776DD2AF15BE700929E9A /* PostLoginServicesBuilder.swift in Sources */,
|
||||
72BDED5E2EA0F6CC00EC3302 /* UserDefaults+PromoCampaigns.swift in Sources */,
|
||||
669776E12AF15BE700929E9A /* OnboardingCoordinator.swift in Sources */,
|
||||
66835C4C2C7F0A2100B158CB /* RecoveryAttempter.swift in Sources */,
|
||||
726A96882EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */,
|
||||
D44B81EE2C71CE170074AD58 /* MemoryWarningObserver.swift in Sources */,
|
||||
669776E22AF15BE700929E9A /* DriveCoreAlertListener.swift in Sources */,
|
||||
D4D84DCD2C300FBB007079B4 /* macOSURLCoordinator.swift in Sources */,
|
||||
@@ -1287,6 +1328,7 @@
|
||||
669776ED2AF15BE700929E9A /* main.m in Sources */,
|
||||
D43A7D402AF4FDB100DCA64E /* NotificationName+Window.swift in Sources */,
|
||||
D8D6FB922B503E2400FB71AE /* Dumper.swift in Sources */,
|
||||
72BDED5B2EA0F53F00EC3302 /* PromoCampaignInteractor.swift in Sources */,
|
||||
A30FD4332CEF2CEB0050E2ED /* SpinningProgressView.swift in Sources */,
|
||||
A30309162CE74CC400087F2E /* ItemRowView.swift in Sources */,
|
||||
D48A113D2D536B2A001618CE /* DeleteAlerter.swift in Sources */,
|
||||
@@ -1298,12 +1340,14 @@
|
||||
A303091A2CE74CC400087F2E /* SyncErrorWindow.swift in Sources */,
|
||||
A318BBBF2D242F970006212A /* SubscriptionService.swift in Sources */,
|
||||
72ADD1912E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift in Sources */,
|
||||
72BDED562EA0EF1500EC3302 /* PromoCampaignBanner.swift in Sources */,
|
||||
A303091C2CE74CC400087F2E /* SyncStateView.swift in Sources */,
|
||||
A303091D2CE74CC400087F2E /* HeaderView.swift in Sources */,
|
||||
A303091E2CE74CC400087F2E /* ItemListView.swift in Sources */,
|
||||
A303091F2CE74CC400087F2E /* FooterView.swift in Sources */,
|
||||
A30309202CE74CC400087F2E /* GlobalProgressStatusItem.swift in Sources */,
|
||||
666D1DDB2D8426FE0048A02F /* FullResyncMetrics.swift in Sources */,
|
||||
7201DEB42E9405D0009FEC92 /* PerformanceMetricsReportAggregator.swift in Sources */,
|
||||
A30309212CE74CC400087F2E /* UserActions.swift in Sources */,
|
||||
A30309232CE74CC400087F2E /* FileTypeAsset.swift in Sources */,
|
||||
A30309242CE74CC400087F2E /* SyncDBObserver.swift in Sources */,
|
||||
@@ -1322,8 +1366,9 @@
|
||||
A3DFAA3F2CE7570D0050AE64 /* QAStateDebuggingView.swift in Sources */,
|
||||
A3DFAA402CE7570D0050AE64 /* QASettingsWindowCoordinator.swift in Sources */,
|
||||
D4989F872C18AADF003DFF5E /* ProtonFile.swift in Sources */,
|
||||
72FFB96A2E8146760013826F /* MeasurementEvent.swift in Sources */,
|
||||
726A96842EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */,
|
||||
D744EE462E059AA3008B005A /* SpinningImage.swift in Sources */,
|
||||
7240FEAB2EA7747000B47EA7 /* PromoCampaignDateResource.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1347,8 +1392,10 @@
|
||||
AB71532424274ED900543720 /* AppDelegate.swift in Sources */,
|
||||
72FFB98A2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */,
|
||||
D458EEE5286DA94200C325C1 /* PostLoginServicesBuilder.swift in Sources */,
|
||||
72BDED5D2EA0F6CC00EC3302 /* UserDefaults+PromoCampaigns.swift in Sources */,
|
||||
66835C4B2C7F0A2100B158CB /* RecoveryAttempter.swift in Sources */,
|
||||
D44B81ED2C71CE170074AD58 /* MemoryWarningObserver.swift in Sources */,
|
||||
726A96872EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */,
|
||||
76CEAF452A6CF695000586F4 /* OnboardingCoordinator.swift in Sources */,
|
||||
D4D84DCC2C300FBB007079B4 /* macOSURLCoordinator.swift in Sources */,
|
||||
D4F28D2A29E97AAE00065CD3 /* DriveCoreAlertListener.swift in Sources */,
|
||||
@@ -1373,6 +1420,7 @@
|
||||
76A2BBDA2AE25F26005B77A2 /* NotificationName+Window.swift in Sources */,
|
||||
D8D6FB912B503E2400FB71AE /* Dumper.swift in Sources */,
|
||||
A30FD4342CEF2CEB0050E2ED /* SpinningProgressView.swift in Sources */,
|
||||
72BDED5A2EA0F53F00EC3302 /* PromoCampaignInteractor.swift in Sources */,
|
||||
A303092D2CE74CC400087F2E /* ItemRowView.swift in Sources */,
|
||||
A35169742D1041F200651795 /* Constants.swift in Sources */,
|
||||
D48A113C2D536B2A001618CE /* DeleteAlerter.swift in Sources */,
|
||||
@@ -1384,12 +1432,14 @@
|
||||
A30309312CE74CC400087F2E /* SyncErrorWindow.swift in Sources */,
|
||||
A318BBBE2D242F970006212A /* SubscriptionService.swift in Sources */,
|
||||
72ADD1902E8FBE0700A7ABB3 /* DBPerformanceMetricsReporter.swift in Sources */,
|
||||
72BDED572EA0EF1500EC3302 /* PromoCampaignBanner.swift in Sources */,
|
||||
A30309332CE74CC400087F2E /* SyncStateView.swift in Sources */,
|
||||
A30309342CE74CC400087F2E /* HeaderView.swift in Sources */,
|
||||
A30309352CE74CC400087F2E /* ItemListView.swift in Sources */,
|
||||
A30309362CE74CC400087F2E /* FooterView.swift in Sources */,
|
||||
A30309372CE74CC400087F2E /* GlobalProgressStatusItem.swift in Sources */,
|
||||
666D1DDA2D8426FE0048A02F /* FullResyncMetrics.swift in Sources */,
|
||||
7201DEB52E9405D0009FEC92 /* PerformanceMetricsReportAggregator.swift in Sources */,
|
||||
A30309382CE74CC400087F2E /* UserActions.swift in Sources */,
|
||||
A35169762D10423300651795 /* RuntimeConfiguration.swift in Sources */,
|
||||
A303093A2CE74CC400087F2E /* FileTypeAsset.swift in Sources */,
|
||||
@@ -1408,8 +1458,9 @@
|
||||
A3DFAA432CE7570D0050AE64 /* QAStateDebuggingView.swift in Sources */,
|
||||
A3DFAA442CE7570D0050AE64 /* QASettingsWindowCoordinator.swift in Sources */,
|
||||
D4989F862C18AADF003DFF5E /* ProtonFile.swift in Sources */,
|
||||
72FFB96B2E8146760013826F /* MeasurementEvent.swift in Sources */,
|
||||
726A96832EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */,
|
||||
D744EE472E059AA3008B005A /* SpinningImage.swift in Sources */,
|
||||
7240FEAA2EA7747000B47EA7 /* PromoCampaignDateResource.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1419,15 +1470,15 @@
|
||||
files = (
|
||||
A35169832D1045A700651795 /* RuntimeConfiguration.swift in Sources */,
|
||||
721D72862E8A7C3000E45B33 /* FetchedResultObserver.swift in Sources */,
|
||||
72FFB98D2E817CAD0013826F /* MeasurementEvent.swift in Sources */,
|
||||
A351697F2D1045A200651795 /* Log+Configuration.swift in Sources */,
|
||||
A3ED69FD2D53488D00FC2A7C /* Log+TestRunner.swift in Sources */,
|
||||
72FFB9882E8179820013826F /* Metrics.xcdatamodeld in Sources */,
|
||||
721D5AFC2E819C8000740E68 /* DBMeasurementCollector.swift in Sources */,
|
||||
72FFB98B2E8179D60013826F /* DBPerformanceMeasurement.swift in Sources */,
|
||||
ABD1909025A30FF7005D3E29 /* FileProviderExtension.swift in Sources */,
|
||||
A351697B2D10458C00651795 /* Constants.swift in Sources */,
|
||||
726A96822EA67D1B00F698F2 /* PerformanceMeasurementEvent.swift in Sources */,
|
||||
72ADD18B2E8ECF4100A7ABB3 /* DBPerformanceMeasurementRepository.swift in Sources */,
|
||||
726A96862EA67D2900F698F2 /* DBPerformanceMeasurementCollector.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -1774,7 +1825,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.9.0;
|
||||
MARKETING_VERSION = 2.10.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = ch.protonmail.drive;
|
||||
PRODUCT_MODULE_NAME = ProtonDriveMac;
|
||||
@@ -1861,7 +1912,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.9.0;
|
||||
MARKETING_VERSION = 2.10.0;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = ch.protonmail.drive;
|
||||
PRODUCT_MODULE_NAME = ProtonDriveMac;
|
||||
@@ -1932,7 +1983,7 @@
|
||||
"@executable_path/../../../../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 13.0;
|
||||
MARKETING_VERSION = 2.9.0;
|
||||
MARKETING_VERSION = 2.10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
OTHER_CODE_SIGN_FLAGS = "";
|
||||
@@ -2635,7 +2686,7 @@
|
||||
version = 0.57.0;
|
||||
};
|
||||
};
|
||||
661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift" */ = {
|
||||
661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/krzyzanowskim/CryptoSwift.git";
|
||||
requirement = {
|
||||
@@ -2643,7 +2694,7 @@
|
||||
minimumVersion = 1.8.3;
|
||||
};
|
||||
};
|
||||
66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams" */ = {
|
||||
66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/jpsim/Yams.git";
|
||||
requirement = {
|
||||
@@ -2651,7 +2702,7 @@
|
||||
minimumVersion = 5.0.0;
|
||||
};
|
||||
};
|
||||
66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */ = {
|
||||
66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ProtonMail/protoncore_ios.git";
|
||||
requirement = {
|
||||
@@ -2659,7 +2710,7 @@
|
||||
version = 33.2.0;
|
||||
};
|
||||
};
|
||||
D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle" */ = {
|
||||
D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/sparkle-project/Sparkle.git";
|
||||
requirement = {
|
||||
@@ -2676,7 +2727,7 @@
|
||||
minimumVersion = 0.0.0;
|
||||
};
|
||||
};
|
||||
D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit" */ = {
|
||||
D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ProtonMail/TrustKit.git";
|
||||
requirement = {
|
||||
@@ -2685,7 +2736,7 @@
|
||||
minimumVersion = 0.0.0;
|
||||
};
|
||||
};
|
||||
D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion" */ = {
|
||||
D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion.git" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/ProtonMail/apple-fusion.git";
|
||||
requirement = {
|
||||
@@ -2715,7 +2766,7 @@
|
||||
};
|
||||
661DC23E2CB94C3C00DECBDE /* CryptoSwift */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift" */;
|
||||
package = 661DC23D2CB94C3C00DECBDE /* XCRemoteSwiftPackageReference "CryptoSwift.git" */;
|
||||
productName = CryptoSwift;
|
||||
};
|
||||
667B32052C69F4E500D15C95 /* PDCore */ = {
|
||||
@@ -2736,49 +2787,57 @@
|
||||
};
|
||||
66E09DFD2E7DA3C30082A1B0 /* Yams */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams.git" */;
|
||||
productName = Yams;
|
||||
};
|
||||
66E09DFF2E7DA42D0082A1B0 /* Yams */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams.git" */;
|
||||
productName = Yams;
|
||||
};
|
||||
66E09E012E7DA4490082A1B0 /* Yams */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
package = 66E09DFC2E7DA3C30082A1B0 /* XCRemoteSwiftPackageReference "Yams.git" */;
|
||||
productName = Yams;
|
||||
};
|
||||
66E09E282E7DB0A40082A1B0 /* ProtonCoreCryptoMultiversionPatchedGoImplementation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreCryptoMultiversionPatchedGoImplementation;
|
||||
};
|
||||
66E09E2A2E7DB0CA0082A1B0 /* ProtonCoreCryptoMultiversionPatchedGoImplementation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreCryptoMultiversionPatchedGoImplementation;
|
||||
};
|
||||
66E09E2C2E7DB0D00082A1B0 /* ProtonCoreCryptoMultiversionPatchedGoImplementation */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreCryptoMultiversionPatchedGoImplementation;
|
||||
};
|
||||
66E09E2E2E7DB2F60082A1B0 /* ProtonCoreQuarkCommands */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreQuarkCommands;
|
||||
};
|
||||
66E09E302E7DB2FC0082A1B0 /* ProtonCoreQuarkCommands */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreQuarkCommands;
|
||||
};
|
||||
66E09E322E7DB3030082A1B0 /* ProtonCoreQuarkCommands */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios" */;
|
||||
package = 66E09E202E7DAD9F0082A1B0 /* XCRemoteSwiftPackageReference "protoncore_ios.git" */;
|
||||
productName = ProtonCoreQuarkCommands;
|
||||
};
|
||||
726691C02E9FCACD00796513 /* PDCoreTestingToolkit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PDCoreTestingToolkit;
|
||||
};
|
||||
72C94D112E9D51FA00FA0038 /* PDCoreTestingToolkit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PDCoreTestingToolkit;
|
||||
};
|
||||
D8262F302C40364B00867704 /* PDLocalization */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PDLocalization;
|
||||
@@ -2793,7 +2852,7 @@
|
||||
};
|
||||
D83C419D2C53A233002EF29C /* Sparkle */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle" */;
|
||||
package = D83C419C2C53A233002EF29C /* XCRemoteSwiftPackageReference "Sparkle.git" */;
|
||||
productName = Sparkle;
|
||||
};
|
||||
D85AB8362C539D5600FFDC10 /* PDFileProvider */ = {
|
||||
@@ -2854,17 +2913,17 @@
|
||||
};
|
||||
D8AB30DA2C621957006A5F7C /* TrustKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit" */;
|
||||
package = D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit.git" */;
|
||||
productName = TrustKit;
|
||||
};
|
||||
D8AB30E12C621A2F006A5F7C /* TrustKit */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit" */;
|
||||
package = D8AB30D92C621957006A5F7C /* XCRemoteSwiftPackageReference "TrustKit.git" */;
|
||||
productName = TrustKit;
|
||||
};
|
||||
D8AB30E42C621ABF006A5F7C /* fusion */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion" */;
|
||||
package = D8AB30E32C621ABF006A5F7C /* XCRemoteSwiftPackageReference "apple-fusion.git" */;
|
||||
productName = fusion;
|
||||
};
|
||||
D8AB30E62C621B7F006A5F7C /* OHHTTPStubs */ = {
|
||||
|
||||
+13
-13
@@ -27,15 +27,6 @@
|
||||
"version" : "1.8.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "ellipticcurvekeypair",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/agens-no/EllipticCurveKeyPair",
|
||||
"state" : {
|
||||
"revision" : "944ae5c89ca045e9f1a113b736706c73fc51d1c2",
|
||||
"version" : "2.0.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "lottie-ios",
|
||||
"kind" : "remoteSourceControl",
|
||||
@@ -59,8 +50,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/ProtonMail/protoncore_ios.git",
|
||||
"state" : {
|
||||
"revision" : "1f7d0eb2c3feb4f28ea0251a4470c44731484637",
|
||||
"version" : "25.3.4"
|
||||
"revision" : "0fdf4c672d898ab8b9048c3b1c36c44a047dd694",
|
||||
"version" : "33.2.0"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -86,8 +77,8 @@
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/getsentry/sentry-cocoa.git",
|
||||
"state" : {
|
||||
"revision" : "c9a692ba837f4832ec7ce6378c071cb91cb55f92",
|
||||
"version" : "8.29.0"
|
||||
"revision" : "9e193ac0b71760603aa666bad7e9e303dd7031a8",
|
||||
"version" : "8.56.2"
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -143,6 +134,15 @@
|
||||
"revision" : "d107d7cc825f38ae2d6dc7c54af71d58145c3506",
|
||||
"version" : "1.0.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "yams",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/Yams.git",
|
||||
"state" : {
|
||||
"revision" : "3d6871d5b4a5cd519adf233fbb576e0a2af71c17",
|
||||
"version" : "5.4.0"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
||||
@@ -38,7 +38,7 @@ import ProtonCoreCryptoMultiversionPatchedGoImplementation
|
||||
/// ↳ `ApplicationEventObserver` - observes all changes relevant to the state of the status window (sync in progress? user logged in?, network reachable?, update available?), and propagates them to the menu bar status item and status window.
|
||||
/// ↳ `ApplicationState` - shared with `AppCoordinator`.
|
||||
/// ↳ `NetworkStateInteractor` - provides updates on whether the network is reachable.
|
||||
/// ↳ `AppUpdateServiceProtocol` - provides updates on whether an app update is available.
|
||||
/// ↳ `AppUpdateServiceProtocol` - provides updaes on whether an app update is available.
|
||||
/// ↳ `SessionVault` - provides updates on when a user logs in.
|
||||
/// ↳ `LoggedInStateReporter` - provides updates on when a user logs in.
|
||||
/// ↳ `GlobalProgressObserver` - provides updates on the state of uploading or downloading operations from the File Provider extension.
|
||||
@@ -48,6 +48,7 @@ import ProtonCoreCryptoMultiversionPatchedGoImplementation
|
||||
/// ↳ `SyncStateDelegate` - updates the `isPaused` and `isOffline` status of `EventsSystemManager` and `DomainOperationsService`.
|
||||
/// ↳ `PDCore.EventsSystemManager` - CoreData (Tower).
|
||||
/// ↳ `DomainOperationsService` Events from the FileProvider.
|
||||
/// ↳ `PromoCampaignInteractor` - provides information about active promo campaigns, used for the banner within the tray app.
|
||||
/// ↳ `MenuBarCoordinator` - logic related to then menu icon and dropdown menu.
|
||||
/// ↳ `DBPerformanceMetricsReporter` - logic related to watching the performance metrics DB and sending
|
||||
/// signals to observability system.
|
||||
@@ -104,6 +105,7 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
private var menuBarCoordinator: MenuBarCoordinator?
|
||||
private var applicationEventObserver: ApplicationEventObserver?
|
||||
private var globalProgressObserver: GlobalProgressObserver?
|
||||
private var promoCampaignInteractor: PromoCampaignInteractorProtocol
|
||||
|
||||
private var initializationCoordinator: InitializationCoordinator?
|
||||
private var onboardingCoordinator: OnboardingCoordinator?
|
||||
@@ -168,6 +170,7 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
accountInfoProvider: initialServices.sessionVault,
|
||||
featureFlags: { featureFlagsAccessor() },
|
||||
fileProviderManagerFactory: SystemFileProviderManagerFactory())
|
||||
let promoCampaignInteractor = PromoCampaignInteractor.shared
|
||||
|
||||
#if HAS_BUILTIN_UPDATER
|
||||
let appUpdateService = SparkleAppUpdateService()
|
||||
@@ -175,16 +178,19 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
let appUpdateService: AppUpdateServiceProtocol? = nil
|
||||
#endif
|
||||
|
||||
self.init(initialServices: initialServices,
|
||||
networkStateService: networkStateService,
|
||||
driveCoreAlertListener: driveCoreAlertListener,
|
||||
loginBuilder: loginBuilder,
|
||||
postLoginServicesBuilder: postLoginServicesBuilder,
|
||||
logContentLoader: logContentLoader,
|
||||
launchOnBoot: launchOnBoot,
|
||||
appUpdateService: appUpdateService,
|
||||
domainOperationsService: domainOperationsService,
|
||||
ddkSessionCommunicator: ddkSessionCommunicator)
|
||||
self.init(
|
||||
initialServices: initialServices,
|
||||
networkStateService: networkStateService,
|
||||
driveCoreAlertListener: driveCoreAlertListener,
|
||||
loginBuilder: loginBuilder,
|
||||
postLoginServicesBuilder: postLoginServicesBuilder,
|
||||
logContentLoader: logContentLoader,
|
||||
launchOnBoot: launchOnBoot,
|
||||
appUpdateService: appUpdateService,
|
||||
domainOperationsService: domainOperationsService,
|
||||
ddkSessionCommunicator: ddkSessionCommunicator,
|
||||
promoCampaignInteractor: promoCampaignInteractor
|
||||
)
|
||||
|
||||
featureFlagsAccessor = { [weak self] in self?.featureFlags }
|
||||
await ddkSessionCommunicator.performInitialSetup()
|
||||
@@ -195,25 +201,31 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG && !canImport(XCTest)
|
||||
#if DEBUG
|
||||
static var counter = 0
|
||||
#endif
|
||||
|
||||
required init(initialServices: InitialServices,
|
||||
networkStateService: NetworkStateInteractor,
|
||||
driveCoreAlertListener: DriveCoreAlertListener,
|
||||
loginBuilder: LoginManagerBuilder,
|
||||
postLoginServicesBuilder: PostLoginServicesBuilder,
|
||||
logContentLoader: LogContentLoader,
|
||||
launchOnBoot: any LaunchOnBootServiceProtocol,
|
||||
appUpdateService: AppUpdateServiceProtocol?,
|
||||
domainOperationsService: DomainOperationsService,
|
||||
ddkSessionCommunicator: SessionRelatedCommunicatorBetweenMainAppAndExtensions) {
|
||||
required init(
|
||||
initialServices: InitialServices,
|
||||
networkStateService: NetworkStateInteractor,
|
||||
driveCoreAlertListener: DriveCoreAlertListener,
|
||||
loginBuilder: LoginManagerBuilder,
|
||||
postLoginServicesBuilder: PostLoginServicesBuilder,
|
||||
logContentLoader: LogContentLoader,
|
||||
launchOnBoot: any LaunchOnBootServiceProtocol,
|
||||
appUpdateService: AppUpdateServiceProtocol?,
|
||||
domainOperationsService: DomainOperationsService,
|
||||
ddkSessionCommunicator: SessionRelatedCommunicatorBetweenMainAppAndExtensions,
|
||||
promoCampaignInteractor: PromoCampaignInteractorProtocol
|
||||
) {
|
||||
|
||||
#if DEBUG && !canImport(XCTest)
|
||||
// Make sure this is only instantiated once
|
||||
#if DEBUG
|
||||
Self.counter += 1
|
||||
assert(Self.counter == 1)
|
||||
|
||||
// Make sure this is only instantiated once only if we're not running tests
|
||||
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil {
|
||||
assert(Self.counter == 1)
|
||||
}
|
||||
#endif
|
||||
|
||||
self.initialServices = initialServices
|
||||
@@ -228,9 +240,9 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
self.ddkSessionCommunicator = ddkSessionCommunicator
|
||||
self.appState = ApplicationState()
|
||||
self.subscriptionService = SubscriptionService(apiService: initialServices.authenticator.apiService)
|
||||
|
||||
self.observationCenter = UserDefaultsObservationCenter(userDefaults: Constants.appGroup.userDefaults)
|
||||
self.performanceMetricsReporter = DBPerformanceMetricsReporter()
|
||||
self.promoCampaignInteractor = promoCampaignInteractor
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -647,6 +659,10 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
sessionVault: postLoginServices.tower.sessionVault
|
||||
)
|
||||
|
||||
applicationEventObserver?.startGeneralSettingsMonitoring(
|
||||
settingsService: postLoginServices.tower.generalSettings
|
||||
)
|
||||
|
||||
let hasPlan = initialServices.sessionVault.userInfo?.hasAnySubscription
|
||||
DriveIntegrityErrorMonitor.configure(with: Constants.appGroup, forUserWithPlan: hasPlan)
|
||||
} catch {
|
||||
@@ -669,10 +685,13 @@ class AppCoordinator: NSObject, ObservableObject {
|
||||
|
||||
appState.setAccountInfo(self.initialServices.sessionVault.getAccountInfo())
|
||||
|
||||
self.applicationEventObserver = ApplicationEventObserver(state: appState,
|
||||
logoutStateService: initialServices,
|
||||
networkStateService: networkStateService,
|
||||
appUpdateService: appUpdateService)
|
||||
self.applicationEventObserver = ApplicationEventObserver(
|
||||
state: appState,
|
||||
logoutStateService: initialServices,
|
||||
networkStateService: networkStateService,
|
||||
appUpdateService: appUpdateService,
|
||||
promoCampaignInteractor: promoCampaignInteractor
|
||||
)
|
||||
|
||||
self.menuBarCoordinator = await MenuBarCoordinator(
|
||||
state: appState,
|
||||
@@ -1446,6 +1465,12 @@ extension AppCoordinator: UserActionsDelegate {
|
||||
}
|
||||
return itemIdentifiers
|
||||
}
|
||||
|
||||
// MARK: - Promotional actions
|
||||
|
||||
func dismissPromoBanner() {
|
||||
promoCampaignInteractor.dismissCampaign()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"provides-namespace" : true
|
||||
}
|
||||
}
|
||||
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-cross-big.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "ic-drive-plus.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "receipt-percent.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+16
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"filename" : "status-promo.pdf",
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
},
|
||||
"properties" : {
|
||||
"preserves-vector-representation" : true,
|
||||
"template-rendering-intent" : "template"
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,64 @@
|
||||
// Copyright (c) 2025 Proton AG
|
||||
//
|
||||
// This file is part of Proton Drive.
|
||||
//
|
||||
// Proton Drive is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Drive is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Foundation
|
||||
import PDCore
|
||||
|
||||
final class PromoCampaignDateResource: DateResource {
|
||||
#if HAS_QA_FEATURES
|
||||
@SettingsStorage(QASettingsConstants.overrideDateForPromoCampaign) var overrideDateForPromoCampaign: String?
|
||||
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
|
||||
dateFormatter.timeZone = .init(identifier: "CET")
|
||||
|
||||
return dateFormatter
|
||||
}()
|
||||
#endif
|
||||
|
||||
private let underlyingDateResource: DateResource
|
||||
|
||||
init(dateResource: DateResource) {
|
||||
self.underlyingDateResource = dateResource
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(dateResource: PlatformCurrentDateResource())
|
||||
}
|
||||
|
||||
func getDate() -> Date {
|
||||
#if HAS_QA_FEATURES
|
||||
guard let overrideDateForPromoCampaign else {
|
||||
return underlyingDateResource.getDate()
|
||||
}
|
||||
|
||||
guard let parsedDate = dateFormatter.date(from: overrideDateForPromoCampaign) else {
|
||||
Log.trace("Possible misconfiguration: have a string for promo date override, but it's not parseable as a date.")
|
||||
return underlyingDateResource.getDate()
|
||||
}
|
||||
|
||||
return parsedDate
|
||||
#else
|
||||
return underlyingDateResource.getDate()
|
||||
#endif
|
||||
}
|
||||
|
||||
func getPastDate() -> Date {
|
||||
underlyingDateResource.getPastDate()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright (c) 2025 Proton AG
|
||||
//
|
||||
// This file is part of Proton Drive.
|
||||
//
|
||||
// Proton Drive is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Drive is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Foundation
|
||||
import ProtonCoreUIFoundations
|
||||
import SwiftUI
|
||||
import PDCore
|
||||
import Combine
|
||||
|
||||
struct PromoCampaignConfiguration {
|
||||
enum BannerIcon {
|
||||
case drivePlus
|
||||
case discount
|
||||
|
||||
var imageName: String {
|
||||
switch self {
|
||||
case .drivePlus:
|
||||
"Promo/ic-drive-plus"
|
||||
case .discount:
|
||||
"Promo/ic-promo-discount"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum TimeRange {
|
||||
/// Campaign should only be active while start < date < end
|
||||
case limitedTime(start: Date, end: Date)
|
||||
/// Campaign should be active after the given date
|
||||
case indefinite(after: Date)
|
||||
/// Campaign should always be active (useful for testing!)
|
||||
case always
|
||||
}
|
||||
|
||||
fileprivate static let activeCampaigns: [PromoCampaignConfiguration] = [
|
||||
PromoCampaignConfiguration(
|
||||
timeRange: .limitedTime(
|
||||
start: Date(timeIntervalSinceReferenceDate: 783860400), // 2025-11-03 12:00 CET
|
||||
end: Date(timeIntervalSinceReferenceDate: 785156400) // 2025-11-18 12:00 CET
|
||||
),
|
||||
backgroundColor: Color(hex: "#D8FF00"),
|
||||
tintColor: Color(hex: "#291C5D"),
|
||||
icon: .discount,
|
||||
text: "Black Friday: 50% off"
|
||||
),
|
||||
PromoCampaignConfiguration(
|
||||
timeRange: .limitedTime(
|
||||
start: Date(timeIntervalSinceReferenceDate: 785156400), // 2025-11-18 12:00 CET
|
||||
end: Date(timeIntervalSinceReferenceDate: 786452400) // 2025-12-03 12:00 CET
|
||||
),
|
||||
backgroundColor: Color(hex: "#D8FF00"),
|
||||
tintColor: Color(hex: "#291C5D"),
|
||||
icon: .discount,
|
||||
text: "Black Friday: 80% off"
|
||||
),
|
||||
PromoCampaignConfiguration(
|
||||
timeRange: .indefinite(
|
||||
after: Date(timeIntervalSinceReferenceDate: 786452400) // 2025-12-03 12:00 CET
|
||||
),
|
||||
backgroundColor: ColorProvider.Primary,
|
||||
tintColor: ColorProvider.White,
|
||||
icon: .drivePlus,
|
||||
text: "Upgrade to Drive Plus"
|
||||
)
|
||||
]
|
||||
|
||||
let timeRange: TimeRange
|
||||
let backgroundColor: Color
|
||||
let tintColor: Color
|
||||
let icon: BannerIcon
|
||||
let text: String
|
||||
}
|
||||
|
||||
protocol PromoCampaignInteractorProtocol {
|
||||
var activeCampaign: AnyPublisher<PromoCampaignConfiguration?, Never> { get }
|
||||
|
||||
func dismissCampaign()
|
||||
}
|
||||
|
||||
final class PromoCampaignInteractor: PromoCampaignInteractorProtocol {
|
||||
var activeCampaign: AnyPublisher<PromoCampaignConfiguration?, Never> {
|
||||
currentlyActiveCampaign.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
@SettingsStorage(UserDefaults.PromoCampaign.hasDismissedBanner.rawValue) private var hasDismissedBanner: Bool?
|
||||
private var currentlyActiveCampaign = CurrentValueSubject<PromoCampaignConfiguration?, Never>(nil)
|
||||
|
||||
private let dateResource: DateResource
|
||||
|
||||
static let shared = PromoCampaignInteractor()
|
||||
|
||||
init(
|
||||
dateResource: DateResource
|
||||
) {
|
||||
self.dateResource = dateResource
|
||||
|
||||
_hasDismissedBanner.configure(with: Constants.appGroup)
|
||||
|
||||
refreshCampaign()
|
||||
}
|
||||
|
||||
private convenience init() {
|
||||
self.init(dateResource: PromoCampaignDateResource())
|
||||
}
|
||||
|
||||
func refreshCampaign(resetBannerDismissal: Bool = false) {
|
||||
if resetBannerDismissal {
|
||||
hasDismissedBanner = false
|
||||
}
|
||||
|
||||
guard (hasDismissedBanner ?? false) == false else {
|
||||
currentlyActiveCampaign.send(.none)
|
||||
return
|
||||
}
|
||||
|
||||
let activeCampaign = getActiveCampaign()
|
||||
currentlyActiveCampaign.send(activeCampaign)
|
||||
}
|
||||
|
||||
func dismissCampaign() {
|
||||
hasDismissedBanner = true
|
||||
currentlyActiveCampaign.send(.none)
|
||||
}
|
||||
|
||||
private func getActiveCampaign() -> PromoCampaignConfiguration? {
|
||||
PromoCampaignConfiguration.activeCampaigns.first { campaign in
|
||||
let currentDate = dateResource.getDate()
|
||||
|
||||
switch campaign.timeRange {
|
||||
case let .limitedTime(start, end):
|
||||
return start <= currentDate && currentDate < end
|
||||
case let .indefinite(start):
|
||||
return start <= currentDate
|
||||
case .always:
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) 2025 Proton AG
|
||||
//
|
||||
// This file is part of Proton Drive.
|
||||
//
|
||||
// Proton Drive is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Drive is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Foundation
|
||||
|
||||
extension UserDefaults {
|
||||
enum PromoCampaign: String {
|
||||
case hasDismissedBanner
|
||||
}
|
||||
}
|
||||
@@ -385,6 +385,33 @@ struct QASettingsView: View {
|
||||
.padding(.bottom, 10)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("To clear override date, remove text from field and submit")
|
||||
Text("The date you input will be parsed in CET time zone")
|
||||
TextField("yyyy-MM-dd HH:mm", text: $vm.overrideDateForPromoCampaign)
|
||||
.onSubmit { vm.refreshPromoCampaign() }
|
||||
.font(.system(size: 11))
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
if let activeCampaign = vm.activeCampaign {
|
||||
HStack {
|
||||
Text("Active campaign:")
|
||||
Spacer()
|
||||
Text(activeCampaign.text)
|
||||
}
|
||||
} else {
|
||||
Text("No active campaign for \(vm.overrideDateForPromoCampaign.isEmpty ? "current" : "override") date")
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(16)
|
||||
} label: {
|
||||
Text("Promotional campaigns")
|
||||
.font(.headline)
|
||||
.padding(.bottom, 10)
|
||||
.padding(.top, 20)
|
||||
}
|
||||
}
|
||||
.frame(width: 350)
|
||||
.padding(20)
|
||||
|
||||
@@ -33,6 +33,7 @@ struct QASettingsConstants {
|
||||
static let disconnectDomainOnSignOut = "disconnectDomainOnSignOut"
|
||||
static let driveDDKEnabledInQASettings = "driveDDKEnabledInQASettings"
|
||||
static let globalProgressStatusMenuEnabled = "globalProgressStatusMenuEnabled"
|
||||
static let overrideDateForPromoCampaign = "overrideDateForPromoCampaign"
|
||||
}
|
||||
|
||||
protocol EventLoopManager: AnyObject {
|
||||
@@ -69,7 +70,7 @@ class QASettingsViewModel: ObservableObject {
|
||||
@SettingsStorage(QASettingsConstants.shouldUpdateEvenOnTestFlight) var shouldUpdateEvenOnTestFlightStorage: Bool?
|
||||
@SettingsStorage(QASettingsConstants.updateChannel) var updateChannelStorage: String?
|
||||
#endif
|
||||
|
||||
|
||||
@Published var shouldFetchEvents: Bool = true {
|
||||
didSet {
|
||||
guard let eventLoopManager else { return }
|
||||
@@ -78,22 +79,22 @@ class QASettingsViewModel: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Published var shouldObfuscateDumps: Bool = false {
|
||||
didSet { shouldObfuscateDumpsStorage = shouldObfuscateDumps }
|
||||
}
|
||||
|
||||
|
||||
@Published var enablePostMigrationCleanup: Bool = false {
|
||||
didSet { requiresPostMigrationCleanup = enablePostMigrationCleanup }
|
||||
}
|
||||
@SettingsStorage(QASettingsConstants.shouldObfuscateDumpsStorage) var shouldObfuscateDumpsStorage: Bool?
|
||||
@SettingsStorage("requiresPostMigrationStep") private var requiresPostMigrationCleanup: Bool?
|
||||
|
||||
|
||||
enum FeatureFlagOptions: String, CaseIterable {
|
||||
case useFF
|
||||
case enabled
|
||||
case disabled
|
||||
|
||||
|
||||
var toBool: Bool? {
|
||||
switch self {
|
||||
case .useFF: return nil
|
||||
@@ -101,7 +102,7 @@ class QASettingsViewModel: ObservableObject {
|
||||
case .disabled: return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
init(bool: Bool?) {
|
||||
switch bool {
|
||||
case nil: self = .useFF
|
||||
@@ -130,11 +131,19 @@ class QASettingsViewModel: ObservableObject {
|
||||
}
|
||||
@SettingsStorage(QASettingsConstants.driveDDKEnabledInQASettings) var driveDDKEnabledStorage: Bool?
|
||||
|
||||
@Published var overrideDateForPromoCampaign: String = "" {
|
||||
didSet { overrideDateForPromoCampaignStorage = overrideDateForPromoCampaign }
|
||||
}
|
||||
|
||||
@SettingsStorage(QASettingsConstants.overrideDateForPromoCampaign) var overrideDateForPromoCampaignStorage: String?
|
||||
@Published var activeCampaign: PromoCampaignConfiguration?
|
||||
|
||||
let parentSessionUID: String
|
||||
let childSessionUID: String
|
||||
let userID: String
|
||||
let clearCredentials: () -> Void
|
||||
|
||||
|
||||
private var cancellables: Set<AnyCancellable> = []
|
||||
private let dumper: Dumper?
|
||||
private let eventLoopManager: EventLoopManager?
|
||||
private let featureFlags: PDCore.FeatureFlagsRepository?
|
||||
@@ -143,6 +152,7 @@ class QASettingsViewModel: ObservableObject {
|
||||
private let metadataStorage: StorageManager?
|
||||
private let eventsStorage: EventStorageManager?
|
||||
private let jailDependencies: (PMAPIService, Client)?
|
||||
private let promoCampaignInteractor: PromoCampaignInteractor
|
||||
|
||||
let applicationEventObserver: ApplicationEventObserver
|
||||
let userActions: UserActions
|
||||
@@ -158,7 +168,8 @@ class QASettingsViewModel: ObservableObject {
|
||||
userActions: UserActions,
|
||||
metadataStorage: StorageManager?,
|
||||
eventsStorage: EventStorageManager?,
|
||||
jailDependencies: (PMAPIService, Client)?
|
||||
jailDependencies: (PMAPIService, Client)?,
|
||||
promoCampaignInteractor: PromoCampaignInteractor
|
||||
) {
|
||||
let suite = Constants.appGroup
|
||||
self._requiresPostMigrationCleanup.configure(with: suite)
|
||||
@@ -186,12 +197,17 @@ class QASettingsViewModel: ObservableObject {
|
||||
self.metadataStorage = metadataStorage
|
||||
self.eventsStorage = eventsStorage
|
||||
self.jailDependencies = jailDependencies
|
||||
self.promoCampaignInteractor = promoCampaignInteractor
|
||||
|
||||
self.shouldObfuscateDumps = shouldObfuscateDumpsStorage ?? false
|
||||
self.enablePostMigrationCleanup = requiresPostMigrationCleanup ?? false
|
||||
self.disconnectDomainOnSignOut = FeatureFlagOptions(bool: disconnectDomainOnSignOutStorage).rawValue
|
||||
self.driveDDKEnabled = FeatureFlagOptions(bool: driveDDKEnabledStorage).rawValue
|
||||
|
||||
self.promoCampaignInteractor.activeCampaign.sink { activeCampaign in
|
||||
self.activeCampaign = activeCampaign
|
||||
}.store(in: &cancellables)
|
||||
|
||||
#if HAS_BUILTIN_UPDATER
|
||||
self.shouldUpdateEvenOnDebugBuild = shouldUpdateEvenOnDebugBuildStorage ?? false
|
||||
self.shouldUpdateEvenOnTestFlight = shouldUpdateEvenOnTestFlightStorage ?? false
|
||||
@@ -475,6 +491,14 @@ class QASettingsViewModel: ObservableObject {
|
||||
dumperError = $0.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func setOverrideDateForPromoCampaign(dateString: String) {
|
||||
self.overrideDateForPromoCampaign = dateString
|
||||
}
|
||||
|
||||
func refreshPromoCampaign() {
|
||||
self.promoCampaignInteractor.refreshCampaign(resetBannerDismissal: true)
|
||||
}
|
||||
}
|
||||
|
||||
extension Notification.Name {
|
||||
|
||||
@@ -77,18 +77,21 @@ final class QASettingsWindowCoordinator: NSObject, NSWindowDelegate {
|
||||
}
|
||||
|
||||
private func configureWindow() {
|
||||
let vm = QASettingsViewModel(signoutManager: signoutManager,
|
||||
sessionStore: sessionStore,
|
||||
mainKeyProvider: mainKeyProvider,
|
||||
appUpdateService: appUpdateService,
|
||||
eventLoopManager: eventLoopManager,
|
||||
featureFlags: featureFlags,
|
||||
dumperDependencies: dumperDependencies,
|
||||
applicationEventObserver: applicationEventObserver,
|
||||
userActions: userActions,
|
||||
metadataStorage: metadataStorage,
|
||||
eventsStorage: eventsStorage,
|
||||
jailDependencies: jailDependencies)
|
||||
let vm = QASettingsViewModel(
|
||||
signoutManager: signoutManager,
|
||||
sessionStore: sessionStore,
|
||||
mainKeyProvider: mainKeyProvider,
|
||||
appUpdateService: appUpdateService,
|
||||
eventLoopManager: eventLoopManager,
|
||||
featureFlags: featureFlags,
|
||||
dumperDependencies: dumperDependencies,
|
||||
applicationEventObserver: applicationEventObserver,
|
||||
userActions: userActions,
|
||||
metadataStorage: metadataStorage,
|
||||
eventsStorage: eventsStorage,
|
||||
jailDependencies: jailDependencies,
|
||||
promoCampaignInteractor: PromoCampaignInteractor.shared
|
||||
)
|
||||
|
||||
let view = QASettingsView(vm: vm)
|
||||
|
||||
|
||||
@@ -62,7 +62,9 @@ struct QAStateDebuggingView_Previews: PreviewProvider {
|
||||
state: ApplicationState.mockWithErrorItems,
|
||||
logoutStateService: nil,
|
||||
networkStateService: nil,
|
||||
appUpdateService: nil)
|
||||
appUpdateService: nil,
|
||||
promoCampaignInteractor: nil
|
||||
)
|
||||
|
||||
QAStateDebuggingView(
|
||||
observer: observer,
|
||||
|
||||
@@ -37,22 +37,25 @@ final class DBPerformanceMetricsReporter: PerformanceMetricsReporter {
|
||||
private let repository: PeformanceMeasurementRepository
|
||||
private let uploadResource: UploadSpeedMetricResource
|
||||
private let downloadResource: DownloadSpeedMetricResource
|
||||
private let dateResource: DateResource
|
||||
private let reportAggregator: PerformanceMetricsReportAggregating
|
||||
private let shouldCleanOnStartup: Bool
|
||||
|
||||
init(
|
||||
repository: PeformanceMeasurementRepository,
|
||||
uploadResource: UploadSpeedMetricResource,
|
||||
downloadResource: DownloadSpeedMetricResource,
|
||||
dateResource: DateResource,
|
||||
inactivityTimer: PausableTimerResource,
|
||||
reportingCycleTimer: PausableTimerResource
|
||||
reportingCycleTimer: PausableTimerResource,
|
||||
reportAggregator: PerformanceMetricsReportAggregating,
|
||||
shouldCleanOnStartup: Bool
|
||||
) {
|
||||
self.repository = repository
|
||||
self.uploadResource = uploadResource
|
||||
self.downloadResource = downloadResource
|
||||
self.dateResource = dateResource
|
||||
self.inactivityTimeout = inactivityTimer
|
||||
self.reportingCycle = reportingCycleTimer
|
||||
self.reportAggregator = reportAggregator
|
||||
self.shouldCleanOnStartup = shouldCleanOnStartup
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
@@ -60,13 +63,14 @@ final class DBPerformanceMetricsReporter: PerformanceMetricsReporter {
|
||||
repository: DBPerformanceMeasurementRepository(),
|
||||
uploadResource: ObservabilityUploadSpeedMetricResource(),
|
||||
downloadResource: ObservabilityDownloadSpeedMetricResource(),
|
||||
dateResource: PlatformCurrentDateResource(),
|
||||
inactivityTimer: CommonRunLoopPausableTimerResource(
|
||||
duration: Constants.inactivityTimeout
|
||||
),
|
||||
reportingCycleTimer: CommonRunLoopPausableTimerResource(
|
||||
duration: Constants.reportingCycleLength
|
||||
)
|
||||
),
|
||||
reportAggregator: PerformanceMetricsReportAggregator(),
|
||||
shouldCleanOnStartup: Self.shouldClearOldMeasurementsByDefault()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -75,12 +79,9 @@ final class DBPerformanceMetricsReporter: PerformanceMetricsReporter {
|
||||
func startReporting() {
|
||||
Log.trace()
|
||||
|
||||
#if !HAS_QA_FEATURES
|
||||
if self.shouldCleanOnStartup {
|
||||
repository.deleteAllMeasurements()
|
||||
#endif
|
||||
|
||||
self.reportingCycle.restart()
|
||||
self.inactivityTimeout.restart()
|
||||
}
|
||||
|
||||
repository.unreportedMeasurementPublisher.sink { [weak self] events in
|
||||
self?.restartTimersIfNeeded()
|
||||
@@ -105,6 +106,7 @@ final class DBPerformanceMetricsReporter: PerformanceMetricsReporter {
|
||||
reportIfNeeded()
|
||||
|
||||
cancellables.forEach { $0.cancel() }
|
||||
cancellables = []
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,11 +140,9 @@ private extension DBPerformanceMetricsReporter {
|
||||
PerformanceOperationType.download: await repository.fetchUnreportedMeasurements(for: .download)
|
||||
]
|
||||
|
||||
let hasMeasurements = measurements.values.contains { !$0.isEmpty }
|
||||
guard hasMeasurements else { return }
|
||||
let report = reportAggregator.buildReport(from: measurements)
|
||||
|
||||
// prepare report: segmented by opreration then by pipeline for ease of reporting
|
||||
let report = measurements.mapValues { makePerPipelineReport(measurements: $0) }
|
||||
guard !report.isEmpty else { return }
|
||||
|
||||
report.forEach { operation, perPipelineReport in
|
||||
switch operation {
|
||||
@@ -172,52 +172,12 @@ private extension DBPerformanceMetricsReporter {
|
||||
}
|
||||
}
|
||||
|
||||
func makePerPipelineReport(measurements: [PerformanceMeasurementEvent]) -> [DriveObservabilityPipeline: Int] {
|
||||
Log.trace()
|
||||
|
||||
// Ideally we'll only ever see one pipeline, but we handle the case where more than one.
|
||||
let groupedByPipeline = Dictionary(grouping: measurements, by: \.pipeline)
|
||||
|
||||
return groupedByPipeline.mapValues {
|
||||
calculateSpeed(measurements: $0)
|
||||
}
|
||||
}
|
||||
|
||||
func calculateSpeed(measurements: [PerformanceMeasurementEvent]) -> Int {
|
||||
Log.trace()
|
||||
|
||||
let measurementTimeRange = getMeasurementTimeRange(from: measurements)
|
||||
let measuredBytes = measurements.map(\.progressInBytes).reduce(0) { $0 + $1 }
|
||||
let speedInKib = Double(measuredBytes) / (Double(1024) * measurementTimeRange)
|
||||
|
||||
return Int(round(speedInKib))
|
||||
}
|
||||
|
||||
func getMeasurementTimeRange(from measurements: [PerformanceMeasurementEvent]) -> TimeInterval {
|
||||
// We expect the measurements to already be sorted by timestamp.
|
||||
// This is done via sortDescriptor in the repository.
|
||||
|
||||
if measurements.count >= 2 {
|
||||
let measurementTimes = measurements.map(\.timestamp)
|
||||
return measurementTimes[0] - measurementTimes[measurements.count - 1]
|
||||
} else if measurements.count == 1 {
|
||||
// It's unexpected that we'll reach this sceneario - the collector should always
|
||||
// write at least a pair of events to the database: a start event with 0 progress,
|
||||
// progress updates (these are optional though) and an end event with the remaining
|
||||
// progress when the file upload completes.
|
||||
|
||||
// If we only have a single measurement, we use the distance to current time
|
||||
// as its duration; the worst case scenario here (a single very small file), we'll divide
|
||||
// its size by ~timeout.
|
||||
Log.warning("Measurement issue: attempting to calculate time range for single measurement", domain: .metrics)
|
||||
let distanceToCurrentTime = measurements[0].timestamp.distance(to: dateResource.getDate().timeIntervalSinceReferenceDate)
|
||||
|
||||
return distanceToCurrentTime == .zero ? 1 : distanceToCurrentTime
|
||||
} else {
|
||||
// We've guaranteed earlier in the call chain that we'd have
|
||||
// at least one element in the measurements list.
|
||||
Log.error("Measuring error: attempted to calculate time range for empty set of measurements", domain: .metrics)
|
||||
return 1
|
||||
}
|
||||
// MARK: - Static helpers
|
||||
private static func shouldClearOldMeasurementsByDefault() -> Bool {
|
||||
#if HAS_QA_FEATURES
|
||||
return false
|
||||
#else
|
||||
return true
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2025 Proton AG
|
||||
//
|
||||
// This file is part of Proton Drive.
|
||||
//
|
||||
// Proton Drive is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Drive is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import Foundation
|
||||
import PDCore
|
||||
|
||||
protocol PerformanceMetricsReportAggregating {
|
||||
func buildReport(
|
||||
from measurements: [PerformanceOperationType: [PerformanceMeasurementEvent]]
|
||||
) -> [PerformanceOperationType: [DriveObservabilityPipeline: Int]]
|
||||
}
|
||||
|
||||
final class PerformanceMetricsReportAggregator: PerformanceMetricsReportAggregating {
|
||||
typealias PerformanceReport = [PerformanceOperationType: [DriveObservabilityPipeline: Int]]
|
||||
|
||||
private let dateResource: DateResource
|
||||
|
||||
init(dateResource: DateResource) {
|
||||
self.dateResource = dateResource
|
||||
}
|
||||
|
||||
convenience init() {
|
||||
self.init(
|
||||
dateResource: PlatformCurrentDateResource()
|
||||
)
|
||||
}
|
||||
|
||||
func buildReport(
|
||||
from measurements: [PerformanceOperationType: [PerformanceMeasurementEvent]]
|
||||
) -> [PerformanceOperationType: [DriveObservabilityPipeline: Int]] {
|
||||
let hasMeasurements = measurements.values.contains { !$0.isEmpty }
|
||||
guard hasMeasurements else { return [:] }
|
||||
|
||||
// prepare report: segmented by opreration then by pipeline for ease of reporting
|
||||
return measurements.mapValues { makePerPipelineReport(measurements: $0) }
|
||||
}
|
||||
}
|
||||
|
||||
private extension PerformanceMetricsReportAggregator {
|
||||
func makePerPipelineReport(measurements: [PerformanceMeasurementEvent]) -> [DriveObservabilityPipeline: Int] {
|
||||
Log.trace()
|
||||
|
||||
// Ideally we'll only ever see one pipeline, but we handle the case where more than one exists.
|
||||
let groupedByPipeline = Dictionary(grouping: measurements, by: \.pipeline)
|
||||
|
||||
return groupedByPipeline.mapValues {
|
||||
calculateSpeed(measurements: $0)
|
||||
}
|
||||
}
|
||||
|
||||
func calculateSpeed(measurements: [PerformanceMeasurementEvent]) -> Int {
|
||||
Log.trace()
|
||||
|
||||
let measurementTimeRange = getMeasurementTimeRange(from: measurements)
|
||||
let measuredBytes = measurements.map(\.progressInBytes).reduce(0) { $0 + $1 }
|
||||
let speedInKib = Double(measuredBytes) / (Double(1024) * measurementTimeRange)
|
||||
|
||||
return Int(round(speedInKib))
|
||||
}
|
||||
|
||||
func getMeasurementTimeRange(from measurements: [PerformanceMeasurementEvent]) -> TimeInterval {
|
||||
// We expect the measurements to already be sorted by timestamp.
|
||||
// This is done via sortDescriptor in the repository.
|
||||
|
||||
if measurements.count >= 2 {
|
||||
let measurementTimes = measurements.map(\.timestamp)
|
||||
let measuredDifference = measurementTimes[0] - measurementTimes[measurements.count - 1]
|
||||
|
||||
// We have a floor of 1 millisecond for the time range.
|
||||
return measuredDifference <= 0.001 ? 0.001 : measuredDifference
|
||||
} else if measurements.count == 1 {
|
||||
// It's unexpected that we'll reach this sceneario - the collector should always
|
||||
// write at least a pair of events to the database: a start event with 0 progress,
|
||||
// progress updates (these are optional though) and an end event with the remaining
|
||||
// progress when the file upload completes.
|
||||
|
||||
// If we only have a single measurement, we use the distance to current time
|
||||
// as its duration; the worst case scenario here (a single very small file), we'll divide
|
||||
// its size by ~timeout.
|
||||
Log.warning("Measurement issue: attempting to calculate time range for single measurement", domain: .metrics)
|
||||
let distanceToCurrentTime = measurements[0].timestamp.distance(to: dateResource.getDate().timeIntervalSinceReferenceDate)
|
||||
|
||||
return distanceToCurrentTime == .zero ? 1 : distanceToCurrentTime
|
||||
} else {
|
||||
// We've guaranteed earlier in the call chain that we'd have
|
||||
// at least one element in the measurements list.
|
||||
Log.error("Measuring error: attempted to calculate time range for empty set of measurements", domain: .metrics)
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,12 @@ class ApplicationEventObserver: ObservableObject {
|
||||
/// Fires every `ElapsedTimeService.timeInterval` seconds, only the dropdown Menu or Status Window are opened.
|
||||
private var elapsedTimeService: ElapsedTimeService?
|
||||
|
||||
/// Fires whenever there's a change to active promo campaigns for the user.
|
||||
private var promoCampaignInteractor: PromoCampaignInteractorProtocol?
|
||||
|
||||
/// Responsible for fetching user config
|
||||
private var generalSettingsService: GeneralSettings?
|
||||
|
||||
private let resyncUpdateSubject = PassthroughSubject<Int, Never>()
|
||||
|
||||
/// Always-on
|
||||
@@ -78,14 +84,18 @@ class ApplicationEventObserver: ObservableObject {
|
||||
/// Only while user is logged in
|
||||
private var userCancellables = Set<AnyCancellable>()
|
||||
|
||||
init(state: ApplicationState,
|
||||
logoutStateService: LoggedInStateReporter?,
|
||||
networkStateService: NetworkStateInteractor?,
|
||||
appUpdateService: AppUpdateServiceProtocol?) {
|
||||
init(
|
||||
state: ApplicationState,
|
||||
logoutStateService: LoggedInStateReporter?,
|
||||
networkStateService: NetworkStateInteractor?,
|
||||
appUpdateService: AppUpdateServiceProtocol?,
|
||||
promoCampaignInteractor: PromoCampaignInteractorProtocol?
|
||||
) {
|
||||
self.state = state
|
||||
self.logoutStateService = logoutStateService
|
||||
self.networkStateService = networkStateService
|
||||
self.appUpdateService = appUpdateService
|
||||
self.promoCampaignInteractor = promoCampaignInteractor
|
||||
|
||||
self.deleteAlerter = DeleteAlerter()
|
||||
|
||||
@@ -118,6 +128,20 @@ class ApplicationEventObserver: ObservableObject {
|
||||
self.subscribetoUserInfo()
|
||||
}
|
||||
|
||||
public func startGeneralSettingsMonitoring(settingsService: GeneralSettings) {
|
||||
Log.trace()
|
||||
|
||||
generalSettingsService = settingsService
|
||||
generalSettingsService?.fetchUserSettings()
|
||||
|
||||
generalSettingsService?.userSettings
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] userSettings in
|
||||
self?.state.setUserSettings(userSettings)
|
||||
}
|
||||
.store(in: &userCancellables)
|
||||
}
|
||||
|
||||
/// - Parameters:
|
||||
/// - all: stop all subscriptions, not just user-specific ones.
|
||||
public func stopMonitoring(dueToSignOut: Bool) {
|
||||
@@ -310,6 +334,38 @@ class ApplicationEventObserver: ObservableObject {
|
||||
}
|
||||
}
|
||||
.store(in: &globalCancellables)
|
||||
|
||||
guard let promoCampaignInteractor else { return }
|
||||
|
||||
Publishers.CombineLatest3(
|
||||
promoCampaignInteractor.activeCampaign,
|
||||
state.$userInfo,
|
||||
state.$userSettings
|
||||
)
|
||||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] campaign, userInfo, userSettings in
|
||||
guard let userInfo, let userSettings else {
|
||||
Log.trace("Promo campaign filtered out because user info or settings aren't available yet")
|
||||
self?.state.setVisibleCampaign(.none)
|
||||
return
|
||||
}
|
||||
|
||||
// In-app notifications are defined as bit 15 of userSettings.news
|
||||
let userHasInAppNotificationsEnabled = ((userSettings.news >> 14) & 1) == 1 ? true : false
|
||||
|
||||
// We don't display campaigns to users who are
|
||||
// * Paying customers
|
||||
// * Delinquent users
|
||||
// * Users who disabled in-app notifications
|
||||
if userInfo.isDelinquent || userInfo.isPaid || !userHasInAppNotificationsEnabled {
|
||||
Log.trace("Promo campaign filtered out because user is not in the target audience")
|
||||
self?.state.setVisibleCampaign(.none)
|
||||
return
|
||||
}
|
||||
|
||||
self?.state.setVisibleCampaign(campaign)
|
||||
}
|
||||
.store(in: &userCancellables)
|
||||
}
|
||||
|
||||
// MARK: - Update availability (appUpdateService)
|
||||
|
||||
@@ -20,6 +20,7 @@ import PDCore
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
import PDLocalization
|
||||
import ProtonCoreUIFoundations
|
||||
|
||||
/// Encapsulates the state of the entire app, driving the UI by publishing updates.
|
||||
class ApplicationState: ObservableObject {
|
||||
@@ -39,14 +40,14 @@ class ApplicationState: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum FullResyncState: CustomStringConvertible, Equatable {
|
||||
case idle
|
||||
case inProgress(Int)
|
||||
case enumerating
|
||||
case completed(hasFileProviderResponded: Bool?)
|
||||
case errored(String)
|
||||
|
||||
|
||||
var isHappening: Bool {
|
||||
switch self {
|
||||
case .idle, .completed: false
|
||||
@@ -57,9 +58,9 @@ class ApplicationState: ObservableObject {
|
||||
/// Displayed in SyncStateView
|
||||
var description: String {
|
||||
switch self {
|
||||
case .idle:
|
||||
case .idle:
|
||||
"Idle"
|
||||
case .inProgress:
|
||||
case .inProgress:
|
||||
"Full resync in progress: downloading data..."
|
||||
case .enumerating:
|
||||
"Full resync in progress: refreshing directories..."
|
||||
@@ -85,10 +86,13 @@ class ApplicationState: ObservableObject {
|
||||
#endif
|
||||
|
||||
init() {
|
||||
#if DEBUG && !canImport(XCTest)
|
||||
#if DEBUG
|
||||
Self.counter += 1
|
||||
// Make sure this is only instantiated once.
|
||||
assert(Self.counter == 1)
|
||||
|
||||
// Make sure this is only instantiated once only if we're not running tests
|
||||
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil {
|
||||
assert(Self.counter == 1)
|
||||
}
|
||||
#endif
|
||||
|
||||
$items
|
||||
@@ -103,11 +107,13 @@ class ApplicationState: ObservableObject {
|
||||
|
||||
@Published private(set) var accountInfo: AccountInfo?
|
||||
@Published private(set) var userInfo: UserInfo?
|
||||
@Published private(set) var userSettings: UserSettings?
|
||||
@Published private(set) var canGetMoreStorage = true
|
||||
@Published private(set) var isOffline = false
|
||||
@Published private(set) var isUpdateAvailable = false
|
||||
/// Percentage of launch sequence that has been completed
|
||||
@Published private(set) var launchCompletion = 0
|
||||
@Published private(set) var visibleCampaign: PromoCampaignConfiguration?
|
||||
|
||||
// MARK: Sync state
|
||||
|
||||
@@ -145,7 +151,7 @@ class ApplicationState: ObservableObject {
|
||||
@Published var deleteCount = 0
|
||||
|
||||
@Published var globalSyncStateDescription: String?
|
||||
// private var fullResyncStateDescription: String = "Full resync"
|
||||
// private var fullResyncStateDescription: String = "Full resync"
|
||||
|
||||
// MARK: Computed
|
||||
|
||||
@@ -253,6 +259,14 @@ class ApplicationState: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func setUserSettings(_ settings: UserSettings?) {
|
||||
self.userSettings = settings
|
||||
}
|
||||
|
||||
func setVisibleCampaign(_ campaign: PromoCampaignConfiguration?) {
|
||||
self.visibleCampaign = campaign
|
||||
}
|
||||
|
||||
deinit {
|
||||
Log.trace()
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ struct ItemListView: View {
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 0) {
|
||||
PromoCampaignBanner(state: state, actions: actions)
|
||||
TimelineView(.periodic(from: Date.now, by: 0.04)) { context in
|
||||
ForEach(Array(state.throttledItems.enumerated()), id: \.element) { index, item in
|
||||
ItemRowView(
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2025 Proton AG
|
||||
//
|
||||
// This file is part of Proton Drive.
|
||||
//
|
||||
// Proton Drive is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// Proton Drive is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Proton Drive. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct PromoCampaignBanner: View {
|
||||
@ObservedObject private var state: ApplicationState
|
||||
private var actions: UserActions
|
||||
|
||||
init(state: ApplicationState, actions: UserActions) {
|
||||
self.state = state
|
||||
self.actions = actions
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
state.visibleCampaign.map { campaign in
|
||||
HStack {
|
||||
Spacer()
|
||||
.frame(width: 16)
|
||||
Label(campaign.text, image: campaign.icon.imageName)
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(campaign.tintColor)
|
||||
Spacer()
|
||||
Button(
|
||||
action: { self.actions.promo.dismissPromoBanner() },
|
||||
label: { Image("Promo/ic-cross").tint(campaign.tintColor) }
|
||||
)
|
||||
.buttonStyle(.borderless)
|
||||
.tint(campaign.tintColor)
|
||||
Spacer()
|
||||
.frame(width: 16)
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture { self.actions.promo.goToPromoPageOnWeb(email: state.accountInfo?.email) }
|
||||
.frame(minHeight: 36, maxHeight: 36)
|
||||
.background(campaign.backgroundColor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,19 +103,23 @@ final class MenuBarCoordinator: NSObject, ObservableObject, NSMenuDelegate {
|
||||
private func statusItemImageName(_ status: ApplicationSyncStatus) -> String {
|
||||
switch status {
|
||||
case .signedOut:
|
||||
"status-signed-out"
|
||||
return "status-signed-out"
|
||||
case .paused:
|
||||
"status-paused"
|
||||
return "status-paused"
|
||||
case .offline:
|
||||
"status-offline"
|
||||
return "status-offline"
|
||||
case .syncing, .enumerating, .launching, .fullResyncInProgress:
|
||||
"status-syncing"
|
||||
return "status-syncing"
|
||||
case .errored:
|
||||
"status-error"
|
||||
return "status-error"
|
||||
case .updateAvailable:
|
||||
"status-update-available"
|
||||
return "status-update-available"
|
||||
case .synced, .fullResyncCompleted:
|
||||
"status-synced"
|
||||
if state.visibleCampaign != nil && !state.items.isEmpty {
|
||||
return "status-promo"
|
||||
} else {
|
||||
return "status-synced"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,9 @@ import PDCore
|
||||
func refreshUserInfo()
|
||||
func signInUsingTestCredentials(login: String, password: String)
|
||||
|
||||
// Promo
|
||||
func dismissPromoBanner()
|
||||
|
||||
// Sync
|
||||
func pauseSyncing()
|
||||
func resumeSyncing()
|
||||
@@ -76,6 +79,7 @@ class UserActions {
|
||||
private weak var delegate: UserActionsDelegate?
|
||||
|
||||
lazy var app = ApplicationActions(delegate: delegate)
|
||||
lazy var promo = PromotionalActions(delegate: delegate)
|
||||
lazy var account = AccountActions(delegate: delegate)
|
||||
lazy var sync = SyncActions(delegate: delegate)
|
||||
lazy var resync = ResyncActions(delegate: delegate)
|
||||
@@ -175,6 +179,23 @@ class UserActions {
|
||||
}
|
||||
}
|
||||
|
||||
class PromotionalActions {
|
||||
private weak var delegate: UserActionsDelegate?
|
||||
|
||||
init(delegate: UserActionsDelegate?) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
func dismissPromoBanner() {
|
||||
delegate?.dismissPromoBanner()
|
||||
}
|
||||
|
||||
func goToPromoPageOnWeb(email: String?) {
|
||||
// reuse LinkActions to go to drive dashboard
|
||||
LinkActions().getMoreStorage(email: email)
|
||||
}
|
||||
}
|
||||
|
||||
class SyncActions {
|
||||
private weak var delegate: UserActionsDelegate?
|
||||
|
||||
|
||||
@@ -32,8 +32,11 @@ class DeleteAlerter {
|
||||
|
||||
init() {
|
||||
Self.counter += 1
|
||||
// Make sure this is only instantiated once.
|
||||
assert(Self.counter == 1)
|
||||
|
||||
// Make sure this is only instantiated once only if we're not running tests
|
||||
if ProcessInfo.processInfo.environment["XCTestConfigurationFilePath"] == nil {
|
||||
assert(Self.counter == 1)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
-11
@@ -22,12 +22,9 @@ import PDFileProviderOperations
|
||||
import ProtonCoreUtilities
|
||||
|
||||
final class DBPerformanceMeasurementCollector: ProgressPerformanceCollector {
|
||||
public let hasOperations: AnyPublisher<Bool, Never>
|
||||
|
||||
private let operationType: PerformanceOperationType
|
||||
private var progresses: Atomic<[Progress: UUID]> = Atomic([:])
|
||||
private var cancellables: [UUID: AnyCancellable] = [:]
|
||||
private let progressCountSubject = CurrentValueSubject<Int, Never>(0)
|
||||
|
||||
private let repository: PeformanceMeasurementRepository
|
||||
private let dateResource: DateResource
|
||||
@@ -38,14 +35,8 @@ final class DBPerformanceMeasurementCollector: ProgressPerformanceCollector {
|
||||
dateResource: DateResource
|
||||
) {
|
||||
self.operationType = operationType
|
||||
|
||||
self.repository = repository
|
||||
self.dateResource = dateResource
|
||||
|
||||
self.hasOperations = progressCountSubject
|
||||
.map { $0 > 0 }
|
||||
.removeDuplicates()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
convenience init(operationType: PerformanceOperationType) {
|
||||
@@ -62,7 +53,6 @@ final class DBPerformanceMeasurementCollector: ProgressPerformanceCollector {
|
||||
let taskID = UUID()
|
||||
|
||||
progresses.mutate { $0.updateValue(taskID, forKey: progress) }
|
||||
progressCountSubject.send(progresses.fetch(\.count))
|
||||
|
||||
// Upload progress events are optional: these will only be reliably sent for
|
||||
// large files.
|
||||
@@ -117,7 +107,6 @@ final class DBPerformanceMeasurementCollector: ProgressPerformanceCollector {
|
||||
)
|
||||
|
||||
cancellables[taskID]?.cancel()
|
||||
|
||||
dict[progress] = nil
|
||||
return
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
<div>
|
||||
<h1>2.10.0</h1>
|
||||
|
||||
<p>
|
||||
- Fixes a rare crash encountered when uploading or downloading many files<br>
|
||||
</p>
|
||||
|
||||
<h1>2.9.0</h1>
|
||||
|
||||
<p>
|
||||
|
||||
Reference in New Issue
Block a user