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>
This commit is contained in:
Till JS 2026-05-13 01:13:27 +02:00
parent 55359c5333
commit 0b2ae167b7
16 changed files with 783 additions and 59 deletions

View file

@ -0,0 +1,82 @@
#!/usr/bin/env swift
// Generiert ein 1024×1024-AppIcon-PNG als Platzhalter.
// forest-green Hintergrund + großes weißes "C" mit Karten-Stack-Andeutung.
//
// Aufruf:
// swift scripts/make-appicon.swift
//
// Schreibt nach: Sources/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png
//
// Dies ist ein Platzhalter vor App-Store-Submission durch ein
// professionelles Icon ersetzen (siehe docs/RELEASE_CHECKLIST.md).
import AppKit
import CoreGraphics
let size = 1024
let scale: CGFloat = 1.0
let cs = CGColorSpaceCreateDeviceRGB()
guard let ctx = CGContext(
data: nil,
width: size, height: size,
bitsPerComponent: 8, bytesPerRow: 0,
space: cs,
bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue
) else {
print("CGContext creation failed")
exit(1)
}
// HSL(142, 76%, 28%) forest primary light. Hand-konvertiert zu sRGB.
let background = CGColor(red: 0.043, green: 0.494, blue: 0.227, alpha: 1)
ctx.setFillColor(background)
ctx.fill(CGRect(x: 0, y: 0, width: size, height: size))
// Subtile Karten-Stack-Schatten hinter dem C
let shadow1 = CGColor(red: 1, green: 1, blue: 1, alpha: 0.08)
let shadow2 = CGColor(red: 1, green: 1, blue: 1, alpha: 0.05)
let cardW = CGFloat(size) * 0.62
let cardH = CGFloat(size) * 0.82
let cardX = (CGFloat(size) - cardW) / 2
let cardY = (CGFloat(size) - cardH) / 2
ctx.setFillColor(shadow2)
ctx.beginPath()
ctx.addPath(CGPath(roundedRect: CGRect(x: cardX + 30, y: cardY - 30, width: cardW, height: cardH),
cornerWidth: 48, cornerHeight: 48, transform: nil))
ctx.fillPath()
ctx.setFillColor(shadow1)
ctx.beginPath()
ctx.addPath(CGPath(roundedRect: CGRect(x: cardX + 15, y: cardY - 15, width: cardW, height: cardH),
cornerWidth: 48, cornerHeight: 48, transform: nil))
ctx.fillPath()
// Großer weißer "C"-Buchstabe Helvetica-Neue-Bold
let attrs: [NSAttributedString.Key: Any] = [
.font: NSFont(name: "HelveticaNeue-Bold", size: 720) ?? NSFont.boldSystemFont(ofSize: 720),
.foregroundColor: NSColor.white,
]
let str = NSAttributedString(string: "C", attributes: attrs)
let line = CTLineCreateWithAttributedString(str)
let bounds = CTLineGetImageBounds(line, ctx)
let tx = (CGFloat(size) - bounds.width) / 2 - bounds.origin.x
let ty = (CGFloat(size) - bounds.height) / 2 - bounds.origin.y
ctx.textPosition = CGPoint(x: tx, y: ty)
CTLineDraw(line, ctx)
// PNG schreiben
guard let cgImage = ctx.makeImage() else {
print("makeImage failed")
exit(1)
}
let bitmap = NSBitmapImageRep(cgImage: cgImage)
guard let data = bitmap.representation(using: .png, properties: [:]) else {
print("PNG encoding failed")
exit(1)
}
let target = URL(fileURLWithPath: "Sources/Resources/Assets.xcassets/AppIcon.appiconset/AppIcon-1024.png")
try data.write(to: target)
print("Wrote \(target.path) (\(data.count) bytes)")
_ = scale // suppress unused warning