Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Code.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
47DDE1F353D72DB6C899D6E0 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EC0A077F6E7EE1EB0853134B /* Foundation.framework */; };
4C7D70012FC8892D0091C7A4 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4C7D6FFA2FC8892D0091C7A4 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
4C7D80012FC8892D0091C7A4 /* FlipcashCore in Frameworks */ = {isa = PBXBuildFile; productRef = 9ABDD1942D9D7B61006B6CDA /* FlipcashCore */; };
4CA1B0022FD00002000AA002 /* FlipcashStore in Frameworks */ = {isa = PBXBuildFile; productRef = 4CA1B0012FD00001000AA001 /* FlipcashStore */; };
4CA1B0032FD00003000AA003 /* FlipcashStore in Frameworks */ = {isa = PBXBuildFile; productRef = 4CA1B0012FD00001000AA001 /* FlipcashStore */; };
08B8484332ED78035E9D399B /* Bugsnag in Frameworks */ = {isa = PBXBuildFile; productRef = 9ADEF1D62DD627C0001B260A /* Bugsnag */; };
4CB225762F77260D0075874E /* FirebaseInstallations in Frameworks */ = {isa = PBXBuildFile; productRef = 4CB225752F77260D0075874E /* FirebaseInstallations */; };
4CB225782F7726500075874E /* FirebaseMessaging in Frameworks */ = {isa = PBXBuildFile; productRef = 4CB225772F7726500075874E /* FirebaseMessaging */; };
Expand Down Expand Up @@ -157,6 +159,7 @@
buildActionMask = 2147483647;
files = (
4C7D80012FC8892D0091C7A4 /* FlipcashCore in Frameworks */,
4CA1B0022FD00002000AA002 /* FlipcashStore in Frameworks */,
08B8484332ED78035E9D399B /* Bugsnag in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand All @@ -176,6 +179,7 @@
508AF9C6D67C4CD29DE10A16 /* TweetNacl in Frameworks */,
9AC0156C2DA576FA0030298E /* opencv2.framework in Frameworks */,
9ABDD1952D9D7B61006B6CDA /* FlipcashCore in Frameworks */,
4CA1B0032FD00003000AA003 /* FlipcashStore in Frameworks */,
9ADEF1D92DD627C6001B260A /* Mixpanel in Frameworks */,
9AE5C0122FA10003004C0DE0 /* SharedCoreKit in Frameworks */,
);
Expand Down Expand Up @@ -322,6 +326,7 @@
name = NotificationService;
packageProductDependencies = (
9ABDD1942D9D7B61006B6CDA /* FlipcashCore */,
4CA1B0012FD00001000AA001 /* FlipcashStore */,
9ADEF1D62DD627C0001B260A /* Bugsnag */,
);
productName = NotificationService;
Expand Down Expand Up @@ -371,6 +376,7 @@
name = Flipcash;
packageProductDependencies = (
9ABDD1942D9D7B61006B6CDA /* FlipcashCore */,
4CA1B0012FD00001000AA001 /* FlipcashStore */,
9AC011172DA4320F0030298E /* FlipcashUI */,
9AD1265D2DA98ADC0048141F /* SQLite */,
9ADEF1D62DD627C0001B260A /* Bugsnag */,
Expand Down Expand Up @@ -1820,6 +1826,10 @@
isa = XCSwiftPackageProductDependency;
productName = FlipcashCore;
};
4CA1B0012FD00001000AA001 /* FlipcashStore */ = {
isa = XCSwiftPackageProductDependency;
productName = FlipcashStore;
};
9AC011172DA4320F0030298E /* FlipcashUI */ = {
isa = XCSwiftPackageProductDependency;
productName = FlipcashUI;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions Flipcash/Core/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
sessionContainer?.session.didEnterBackground()
container.preferences.appDidEnterBackground()
sessionContainer?.pushController.clearBadgeCount()
closeDatabase()
case .active:
logger.info("scenePhase → active")
container.client.warmUpChannel()
Expand All @@ -147,6 +148,38 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}

/// Checkpoints and closes the store on the way to the background.
///
/// `.active` has no counterpart on purpose: the connections reopen on the first
/// read after the app comes back, so a return that never happens costs nothing and
/// a close that lands at an awkward moment repairs itself.
///
/// The background-task assertion covers the checkpoint, which is file I/O
/// proportional to the write-ahead log. Being suspended partway through it leaves
/// the log on disk for the next launch to replay rather than damaging the store, so
/// the assertion buys a faster next launch, not correctness.
private func closeDatabase() {
guard let database = sessionContainer?.database else {
return
}

var identifier = UIBackgroundTaskIdentifier.invalid
identifier = UIApplication.shared.beginBackgroundTask(withName: "database.close") {
UIApplication.shared.endBackgroundTask(identifier)
identifier = .invalid
}

do {
try database.close()
} catch {
logger.error("Failed to close the database", metadata: ["error": "\(error)"])
}

if identifier != .invalid {
UIApplication.shared.endBackgroundTask(identifier)
}
}

// MARK: - Deep Links -

func handleOpenURL(url: URL) {
Expand Down
1 change: 1 addition & 0 deletions Flipcash/Core/Controllers/BlocklistController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import FlipcashCore
import FlipcashStore

nonisolated private let logger = Logger(label: "flipcash.blocklist-controller")

Expand Down
1 change: 1 addition & 0 deletions Flipcash/Core/Controllers/ContactDirectory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Contacts
import Foundation
import FlipcashCore
import FlipcashStore

nonisolated private let logger = Logger(label: "flipcash.contact-directory")

Expand Down
7 changes: 4 additions & 3 deletions Flipcash/Core/Controllers/ContactSyncController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import Contacts
import Foundation
import FlipcashCore
import FlipcashStore

nonisolated private let logger = Logger(label: "flipcash.contact-sync-controller")

Expand Down Expand Up @@ -50,7 +51,7 @@ final class ContactSyncController {
/// Set once, during the user's first contact scan, to the number of the
/// user's contacts the server matched. Gated on the durable `contactsConnected`
/// flag (UserDefaults, not the DB) so it fires once per device and never
/// re-fires after a `SQLiteVersion` rebuild, later syncs, or screen opens.
/// re-fires after a `schemaVersion` rebuild, later syncs, or screen opens.
var onFlipcashMatchCount: Int?

nonisolated private var ownerKeyPair: KeyPair {
Expand Down Expand Up @@ -275,7 +276,7 @@ final class ContactSyncController {
await resolveDirectory()

// Fire the one-time "already on Flipcash" dialog on the first connect.
// The flag lives in UserDefaults, not the DB, so a `SQLiteVersion`
// The flag lives in UserDefaults, not the DB, so a `schemaVersion`
// rebuild never re-fires it for an existing user.
await MainActor.run {
guard UserDefaults.contactsConnected != true else { return }
Expand Down Expand Up @@ -545,7 +546,7 @@ extension ContactSyncController: DMContactNaming {
extension UserDefaults {
/// `true` once this device has completed its first contact connect. Gates the
/// one-time "already on Flipcash" dialog; persisted here rather than in the
/// per-account SQLite store so a `SQLiteVersion` rebuild doesn't re-fire it.
/// per-account SQLite store so a `schemaVersion` rebuild doesn't re-fire it.
@Defaults(.contactsConnected)
static var contactsConnected: Bool?
}
18 changes: 3 additions & 15 deletions Flipcash/Core/Controllers/ConversationController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import Foundation
import SwiftUI
import FlipcashCore
import FlipcashStore

nonisolated private let logger = Logger(label: "flipcash.conversation-controller")

Expand Down Expand Up @@ -450,7 +451,7 @@ final class ConversationController {
private func hydrateIfUnknown(_ event: ConversationStreamEvent) {
let conversationID: ConversationID
switch event {
case .newMessages(let id, _), .chatEvents(let id, _), .lastActivityChanged(let id, _), .readPointersChanged(let id, _):
case .chatEvents(let id, _), .lastActivityChanged(let id, _), .readPointersChanged(let id, _):
conversationID = id
case .metadataRefresh:
return
Expand Down Expand Up @@ -613,22 +614,9 @@ final class ConversationController {
/// post-`apply` state so monotonic rules (read pointers) hold.
private func persist(event: ConversationStreamEvent) {
switch event {
case .newMessages(let conversationID, let messages):
case .chatEvents(let conversationID, let events):
// Read before the write: the newest stored id is the analytics watermark,
// and after the upsert it would already include this batch.
let countedThrough = (try? database.newestMessageID(conversationID: conversationID)) ?? nil
let (reconciled, pairs) = reconciledForPersist(messages, in: conversationID)
let ok = persist(operation: "upsert-messages") { try database.upsertConversationMessages(reconciled, conversationID: conversationID) }
if ok {
commitReconciled(pairs, in: conversationID)
receipts.countReceived(reconciled, countedThrough: countedThrough, delivery: .live)
} else {
// The delivered batch is in neither the DB nor the store — refetch it from the event log.
scheduleGapCatchUp(conversationID)
}
refreshFeedPreview(for: conversationID)
persistConversation(conversationID)
case .chatEvents(let conversationID, let events):
let countedThrough = (try? database.newestMessageID(conversationID: conversationID)) ?? nil
let (reconciled, pairs) = reconciledForPersist(events.flatMap { $0.mutations.map(\.message) }, in: conversationID)
// Messages + the advanced cursor persist atomically. `store.apply` already advanced the
Expand Down
154 changes: 0 additions & 154 deletions Flipcash/Core/Controllers/Database/Database.swift

This file was deleted.

1 change: 1 addition & 0 deletions Flipcash/Core/Controllers/HistoryController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import FlipcashCore
import FlipcashStore

private let logger = Logger(label: "flipcash.history-controller")

Expand Down
2 changes: 1 addition & 1 deletion Flipcash/Core/Controllers/Onramp/CoinbaseOrderEmail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import FlipcashCore
///
/// The email flow writes the fallback when `requireCoinbaseEmailVerification`
/// is off; logout clears it. It lives in UserDefaults rather than SQLite
/// because the server never sees it — a `SQLiteVersion` rebuild (which
/// because the server never sees it — a `schemaVersion` rebuild (which
/// restores only server data) would lose it.
enum CoinbaseOrderEmail {

Expand Down
1 change: 1 addition & 0 deletions Flipcash/Core/Controllers/RatesController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Foundation
// or these are migrated to AsyncSequence.
@preconcurrency import Combine
import FlipcashCore
import FlipcashStore

nonisolated private let logger = Logger(label: "flipcash.rates-controller")

Expand Down
1 change: 1 addition & 0 deletions Flipcash/Core/Screens/Main/AddMoney/AddMoneyGate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import Foundation
import FlipcashCore
import FlipcashStore

/// Read access to every balance the launch gate weighs.
@MainActor
Expand Down
Loading
Loading