cards-native/Sources/Core/Sync/PendingShareStore.swift
Till JS 0b2ae167b7 v0.8.0 — Phase β-7 App-Store-Vorbereitung
Feature-komplett für TestFlight. App-Icon-Platzhalter, Siri-Shortcut,
Share-Extension, Release-Checklist mit allen externen Apple-Schritten.

- scripts/make-appicon.swift: CoreGraphics-basierter Generator für
  1024×1024 forest-green PNG mit "C"-Letter und Karten-Stack-Schatten
- Asset-Catalog auf Single-Size-AppIcon-Pattern umgestellt
- StudyCardsIntent + CardsAppShortcuts (App Intents): Siri-
  Shortcut "Karten lernen mit Cards" / "Mit Cards lernen"
- CardsShareExtension Target: ShareViewController (UIKit-Bootstrap +
  SwiftUI-Hosting), ShareEditorView mit Text-Edit
- PendingShare + PendingShareStore shared in App-Group
  group.ev.mana.cards
- DeckListView zeigt PendingShare-Banner; Tap navigiert zu
  PendingShareConsumeView mit Deck-Picker + Front/Back-Felder, Submit
  → POST /cards, danach store.remove
- Info.plist: NSPhotoLibraryUsageDescription für Image-Occlusion-
  Picker, NSUserActivityTypes für Universal-Links
- docs/RELEASE_CHECKLIST.md mit externen Schritten: Apple-Developer-
  Portal, App-IDs, App-Group, AASA, Xcode-Archive, TestFlight-Plan,
  App-Store-Connect-Felder, Compliance-Verifikation
- UI-Test robuster (akzeptiert Login oder Decks/Entdecken als
  Launch-Erfolg, unabhängig vom Simulator-Keychain-State)
- 35 Tests + 1 UI-Test grün, alle drei Targets bauen

App-Store-Submission selbst ist externe Aktion und passiert nicht
durch dieses Repo — Schritte in docs/RELEASE_CHECKLIST.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 01:13:27 +02:00

61 lines
2.1 KiB
Swift

import Foundation
/// Inbox für Share-Extension. Die Extension persistiert hier, die
/// Haupt-App liest beim Start und zeigt einen Banner mit
/// " Als Karte speichern". Shared App-Group-Container.
struct PendingShare: Codable, Identifiable, Hashable, Sendable {
let id: String
let text: String
let sourceURL: String?
let capturedAt: Date
init(text: String, sourceURL: String? = nil, capturedAt: Date = .now) {
id = "\(capturedAt.timeIntervalSince1970)-\(UUID().uuidString.prefix(8))"
self.text = text
self.sourceURL = sourceURL
self.capturedAt = capturedAt
}
}
enum PendingShareStore {
static let appGroupID = "group.ev.mana.cards"
static let filename = "pending-shares.json"
static var url: URL? {
FileManager.default
.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)?
.appendingPathComponent(filename)
}
/// FIFO-Liste aller noch nicht konsumierten Shares.
static func readAll() -> [PendingShare] {
guard let url, let data = try? Data(contentsOf: url) else { return [] }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
return (try? decoder.decode([PendingShare].self, from: data)) ?? []
}
/// Append-only-Write. Bei Concurrent-Writes aus Extension + Haupt-App
/// kann ein Eintrag verloren gehen akzeptabel, weil Extension nur
/// schreibt wenn User aktiv "Teilen" tippt.
static func append(_ share: PendingShare) {
guard let url else { return }
var all = readAll()
all.append(share)
write(all)
}
/// Entfernt einen Eintrag (wenn die Haupt-App ihn als Karte gespeichert hat).
static func remove(id: String) {
let all = readAll().filter { $0.id != id }
write(all)
}
private static func write(_ shares: [PendingShare]) {
guard let url else { return }
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
guard let data = try? encoder.encode(shares) else { return }
try? data.write(to: url, options: .atomic)
}
}