Release 1.0.3: refine tablet layouts, pointer selection and account sync cleanup
Build iOS / build (push) Successful in 4m45s
Build App Bundle / build (push) Successful in 7m7s

This commit is contained in:
Ayushya Amitabh
2026-10-09 00:36:42 -04:00
parent 79085874a1
commit 0a4608e6c3
67 changed files with 1740 additions and 451 deletions
+6
View File
@@ -607,6 +607,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -680,6 +681,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -708,6 +710,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -736,6 +739,7 @@
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)";
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo.ShareExtension;
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = iphoneos;
@@ -870,6 +874,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@@ -893,6 +898,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
DEVELOPMENT_TEAM = Q3JLTAG9PV;
PRODUCT_BUNDLE_IDENTIFIER = dev.ayushya.noo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
+12
View File
@@ -149,6 +149,18 @@ enum NativeServices {
// Fire and forget, like Android: the status stream reports the result.
Task { _ = await coordinator.resolveConflict(conflict, resolution: resolution, creds: creds) }
}
case "removeAccountData":
guard let accountId = args["accountId"] as? String else {
return result(FlutterError(code: "bad_args", message: "Missing accountId", details: nil))
}
Task {
do {
try await coordinator.removeAccountData(accountId: accountId)
await MainActor.run { result(nil) }
} catch {
await MainActor.run { result(FlutterError(code: "cleanup_failed", message: error.localizedDescription, details: nil)) }
}
}
case "removeLocalSync":
guard let accountId = args["accountId"] as? String, let path = args["path"] as? String else {
return result(FlutterError(code: "bad_args", message: "Missing required arguments", details: nil))
@@ -194,6 +194,16 @@ final class SyncCoordinator {
/// removal under the same mutex as runs - a run still holding the old
/// state would otherwise write it back over the removal (and a transfer
/// finishing after it would leave a file the state doesn't know about).
func removeAccountData(accountId: String) async throws {
cancel(accountId: accountId, forget: true)
try await mutex.withLock {
try store.removeAccountData(accountId: accountId)
for conflict in bus.snapshot().conflicts where conflict.accountId == accountId {
bus.removeConflict(accountId: accountId, fileId: conflict.fileId)
}
}
}
func removeLocalSync(accountId: String, path: String) async {
cancelRun(accountId: accountId, invalidateQueued: true)
_ = try? await mutex.withLock {
@@ -108,6 +108,13 @@ final class SyncStore {
if changed { _ = write(Array(current).sorted(), to: file(accountId, "missing.json")) }
}
func removeAccountData(accountId: String) throws {
for directory in ["sync", "sync-state"] {
let url = base.appendingPathComponent(directory).appendingPathComponent(accountId)
if fileManager.fileExists(atPath: url.path) { try fileManager.removeItem(at: url) }
}
}
// MARK: - Removing a path's mirror
/// Deletes [path]'s local mirror (a file, or a whole folder's worth) and
+15
View File
@@ -237,6 +237,21 @@ final class SyncEngineTests: XCTestCase {
// MARK: - The happy path
func testRemovingAccountDeletesMirrorAndStateButPreservesOtherAccounts() async throws {
seedServer()
_ = await runner.run(config, force: false)
let other = store.syncRoot(accountId: "other").appendingPathComponent("keep.txt")
try Data("keep".utf8).write(to: other)
let mirrorPath = store.base.appendingPathComponent("sync/acct")
let statePath = store.base.appendingPathComponent("sync-state/acct")
try store.removeAccountData(accountId: "acct")
XCTAssertFalse(FileManager.default.fileExists(atPath: mirrorPath.path))
XCTAssertFalse(FileManager.default.fileExists(atPath: statePath.path))
XCTAssertTrue(FileManager.default.fileExists(atPath: other.path))
XCTAssertTrue(try store.loadState(accountId: "acct").isEmpty)
try store.removeAccountData(accountId: "acct")
}
func testFirstSyncDownloadsEverythingAndMirrorsFolders() async {
seedServer()
FakeDav.mkdir("/Docs/empty")