Add tests for 1.1.0 features
This commit is contained in:
@@ -17,7 +17,11 @@ struct BackupRecord: Identifiable {
|
||||
class BackupService {
|
||||
static let shared = BackupService()
|
||||
|
||||
private let fileManager = FileManager.default
|
||||
private let fileManager: FileManager
|
||||
private let dateProvider: () -> Date
|
||||
private let localBaseDirectoryProvider: () -> URL?
|
||||
private let iCloudBaseDirectoryProvider: () -> URL?
|
||||
private let exportProvider: () -> String
|
||||
private let dateFormatter: DateFormatter = {
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "yyyyMMdd-HHmmss"
|
||||
@@ -25,16 +29,42 @@ class BackupService {
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private init() {}
|
||||
private init() {
|
||||
fileManager = .default
|
||||
dateProvider = Date.init
|
||||
localBaseDirectoryProvider = { [fileManager] in
|
||||
fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first
|
||||
}
|
||||
iCloudBaseDirectoryProvider = { [fileManager] in
|
||||
fileManager.url(forUbiquityContainerIdentifier: nil)
|
||||
}
|
||||
exportProvider = {
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
let sources = BackupService.fetchAllSources(in: context)
|
||||
let categories = BackupService.fetchAllCategories(in: context)
|
||||
return ExportService.shared.exportToJSON(sources: sources, categories: categories)
|
||||
}
|
||||
}
|
||||
|
||||
init(
|
||||
fileManager: FileManager = .default,
|
||||
dateProvider: @escaping () -> Date,
|
||||
localBaseDirectoryProvider: @escaping () -> URL?,
|
||||
iCloudBaseDirectoryProvider: @escaping () -> URL?,
|
||||
exportProvider: @escaping () -> String
|
||||
) {
|
||||
self.fileManager = fileManager
|
||||
self.dateProvider = dateProvider
|
||||
self.localBaseDirectoryProvider = localBaseDirectoryProvider
|
||||
self.iCloudBaseDirectoryProvider = iCloudBaseDirectoryProvider
|
||||
self.exportProvider = exportProvider
|
||||
}
|
||||
|
||||
func createBackup(retentionCount: Int, includeICloud: Bool) -> [BackupRecord] {
|
||||
let timestamp = dateFormatter.string(from: Date())
|
||||
let timestamp = dateFormatter.string(from: dateProvider())
|
||||
let fileName = "backup-\(timestamp).json"
|
||||
|
||||
let context = CoreDataStack.shared.viewContext
|
||||
let sources = fetchAllSources(in: context)
|
||||
let categories = fetchAllCategories(in: context)
|
||||
let content = ExportService.shared.exportToJSON(sources: sources, categories: categories)
|
||||
let content = exportProvider()
|
||||
|
||||
var records: [BackupRecord] = []
|
||||
|
||||
@@ -67,7 +97,7 @@ class BackupService {
|
||||
}
|
||||
|
||||
private func backupDirectory() -> URL? {
|
||||
guard let base = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else {
|
||||
guard let base = localBaseDirectoryProvider() else {
|
||||
return nil
|
||||
}
|
||||
let dir = base.appendingPathComponent("Backups", isDirectory: true)
|
||||
@@ -76,7 +106,7 @@ class BackupService {
|
||||
}
|
||||
|
||||
private func iCloudBackupDirectory() -> URL? {
|
||||
guard let base = fileManager.url(forUbiquityContainerIdentifier: nil) else { return nil }
|
||||
guard let base = iCloudBaseDirectoryProvider() else { return nil }
|
||||
let dir = base.appendingPathComponent("Documents/Backups", isDirectory: true)
|
||||
ensureDirectoryExists(dir)
|
||||
return dir
|
||||
@@ -137,13 +167,13 @@ class BackupService {
|
||||
return dateFormatter.date(from: dateString)
|
||||
}
|
||||
|
||||
private func fetchAllSources(in context: NSManagedObjectContext) -> [InvestmentSource] {
|
||||
private static func fetchAllSources(in context: NSManagedObjectContext) -> [InvestmentSource] {
|
||||
let request: NSFetchRequest<InvestmentSource> = InvestmentSource.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \InvestmentSource.name, ascending: true)]
|
||||
return (try? context.fetch(request)) ?? []
|
||||
}
|
||||
|
||||
private func fetchAllCategories(in context: NSManagedObjectContext) -> [Category] {
|
||||
private static func fetchAllCategories(in context: NSManagedObjectContext) -> [Category] {
|
||||
let request: NSFetchRequest<Category> = Category.fetchRequest()
|
||||
request.sortDescriptors = [NSSortDescriptor(keyPath: \Category.name, ascending: true)]
|
||||
return (try? context.fetch(request)) ?? []
|
||||
|
||||
@@ -49,6 +49,13 @@ class IAPService: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
func setPremiumForTesting(_ value: Bool, familyShared: Bool = false) {
|
||||
isPremium = value
|
||||
isFamilyShared = familyShared
|
||||
}
|
||||
#endif
|
||||
|
||||
deinit {
|
||||
updateListenerTask?.cancel()
|
||||
}
|
||||
|
||||
@@ -8,38 +8,59 @@ final class ReviewPromptService {
|
||||
private let checkInCountKey = "reviewPromptCheckinCount"
|
||||
private let minCheckInsBetweenPrompts = 3
|
||||
private let minDaysBetweenPrompts = 90
|
||||
private let userDefaults: UserDefaults
|
||||
private let dateProvider: () -> Date
|
||||
private let reviewRequestHandler: () -> Void
|
||||
|
||||
private init() {}
|
||||
private init() {
|
||||
userDefaults = .standard
|
||||
dateProvider = Date.init
|
||||
reviewRequestHandler = ReviewPromptService.defaultReviewRequestHandler
|
||||
}
|
||||
|
||||
init(
|
||||
userDefaults: UserDefaults,
|
||||
dateProvider: @escaping () -> Date,
|
||||
reviewRequestHandler: @escaping () -> Void
|
||||
) {
|
||||
self.userDefaults = userDefaults
|
||||
self.dateProvider = dateProvider
|
||||
self.reviewRequestHandler = reviewRequestHandler
|
||||
}
|
||||
|
||||
func recordMonthlyCheckInCompleted() {
|
||||
let currentCount = UserDefaults.standard.integer(forKey: checkInCountKey)
|
||||
UserDefaults.standard.set(currentCount + 1, forKey: checkInCountKey)
|
||||
let currentCount = userDefaults.integer(forKey: checkInCountKey)
|
||||
userDefaults.set(currentCount + 1, forKey: checkInCountKey)
|
||||
requestReviewIfEligible()
|
||||
}
|
||||
|
||||
private func requestReviewIfEligible() {
|
||||
let now = Date()
|
||||
if let lastPrompt = UserDefaults.standard.object(forKey: lastPromptKey) as? Date {
|
||||
let now = dateProvider()
|
||||
if let lastPrompt = userDefaults.object(forKey: lastPromptKey) as? Date {
|
||||
let daysSince = now.timeIntervalSince(lastPrompt) / 86_400
|
||||
if daysSince < Double(minDaysBetweenPrompts) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
let count = UserDefaults.standard.integer(forKey: checkInCountKey)
|
||||
let count = userDefaults.integer(forKey: checkInCountKey)
|
||||
guard count >= minCheckInsBetweenPrompts else { return }
|
||||
|
||||
requestReview()
|
||||
}
|
||||
|
||||
private func requestReview() {
|
||||
reviewRequestHandler()
|
||||
userDefaults.set(dateProvider(), forKey: lastPromptKey)
|
||||
userDefaults.set(0, forKey: checkInCountKey)
|
||||
}
|
||||
|
||||
private static func defaultReviewRequestHandler() {
|
||||
guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
|
||||
if #available(iOS 18.0, *) {
|
||||
AppStore.requestReview(in: scene)
|
||||
} else {
|
||||
SKStoreReviewController.requestReview(in: scene)
|
||||
}
|
||||
UserDefaults.standard.set(Date(), forKey: lastPromptKey)
|
||||
UserDefaults.standard.set(0, forKey: checkInCountKey)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,18 @@ class ShareService {
|
||||
|
||||
private init() {}
|
||||
|
||||
static func buildMonthlyCheckInShareText(summary: MonthlySummary, appName: String) -> String {
|
||||
"""
|
||||
\(summary.periodLabel) Check-in
|
||||
Starting: \(summary.formattedStartingValue)
|
||||
Ending: \(summary.formattedEndingValue)
|
||||
Contributions: \(summary.formattedContributions)
|
||||
Net performance: \(summary.formattedNetPerformance) (\(summary.formattedNetPerformancePercentage))
|
||||
|
||||
Shared from \(appName)
|
||||
"""
|
||||
}
|
||||
|
||||
func shareText(_ content: String) {
|
||||
guard let viewController = ShareService.topViewController() else { return }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user