4 Commits

Author SHA1 Message Date
Millian Lamiaux
a843a2bd20 feat(home): custom SVG zone icons for body-zone cards
All checks were successful
CI / Detect Changes (pull_request) Successful in 4s
CI / YouTube Worker (pull_request) Has been skipped
CI / Deploy (pull_request) Has been skipped
PR → Build → devicectl Deploy → LGTM / Build & Deploy to iPhone (devicectl) (pull_request) Successful in 6m58s
PR → Build → devicectl Deploy → LGTM / Wait for LGTM comment (pull_request) Successful in 31s
Replace the hand-drawn Path silhouettes in ZoneHighlightIcon with
polished vector SVG artwork (upper/lower/full body figures).

- Add ZoneIconUpper/Lower/Full.imagesets to Assets.xcassets with
  preserves-vector-representation for crisp rendering at any size
- Rewrite ZoneHighlightIcon to load the SVG assets via Image with
  scaledToFit, preserving the zone: String API so ZoneCard is unchanged

Verified: xcodegen + xcodebuild (Swift 6 strict concurrency) succeeds;
rendered on iPhone 17 Pro simulator.
2026-07-20 14:20:48 +02:00
semantic-release-bot
1ada238004 chore(release): 1.0.1 [skip ci]
## [1.0.1](https://gitea.1000co.fr/millianlmx/tabatago/compare/v1.0.0...v1.0.1) (2026-07-19)

### Bug Fixes

* **ci:** pr-iphone-deploy — cache, secrets, LGTM regex, concurrency, watch build, scope ([#12](#12)) ([6937a36](6937a36ccd))
2026-07-19 19:00:24 +00:00
6937a36ccd fix(ci): pr-iphone-deploy — cache, secrets, LGTM regex, concurrency, watch build, scope (#12)
All checks were successful
Admin Web Docker / Docker Build Validation (push) Has been skipped
Admin Web Docker / Admin Web Tests (push) Successful in 33s
CI / Detect Changes (push) Successful in 3s
Admin Web Docker / Semantic Release (push) Successful in 11s
CI / YouTube Worker (push) Has been skipped
Admin Web Docker / Build & Push Docker Image (push) Successful in 22s
CI / Deploy (push) Has been skipped
2026-07-19 20:59:33 +02:00
semantic-release-bot
32ccbccf5b chore(release): 1.0.0 [skip ci]
# 1.0.0 (2026-07-19)

### Bug Fixes

* add /opt/homebrew/bin to PATH for all steps ([44cebf8](44cebf834c))
* add 6s timeout to MusicService Supabase fetch for offline fallback ([057fbb3](057fbb3c9a))
* add DEVELOPMENT_TEAM to project.yml ([86fb683](86fb683428))
* add DEVELOPMENT_TEAM via xcodebuild CLI ([ebfaa15](ebfaa15e38))
* add HealthKit entitlement and regenerate Xcode project to resolve NSInvalidArgumentException ([9943dce](9943dce82d))
* add missing getPopularWorkouts export to data layer ([569a9e1](569a9e178f))
* add missing Workout fields to program workouts and guard against undefined level ([f11eb6b](f11eb6b9ae))
* add NSSupportsLiveActivities to main app target + cleanup info plists ([f5a36f0](f5a36f0a26))
* add shared model sources to TabataGoWidget target ([1bb6a54](1bb6a544e4))
* add TabataGo scheme to project.yml (xcodegen 2.45.4 compatibility) ([cfe8e8c](cfe8e8cd1b))
* add widget target, push entitlement, error handling, force-unwrap ([a173670](a17367081c))
* add xcodebuild -list debug, use -project flag ([fdf4b6d](fdf4b6d558))
* align bundleIdPrefix with Firebase app (com.tabatago → fr.millianlmx.tabatago) ([52346fb](52346fb4ac))
* align program detail screen with Dark Medical design system ([e0e02c4](e0e02c4550)), closes [#000](https://gitea.1000co.fr/millianlmx/tabatago/issues/000)
* change bundle ID from com.tabatago.app to fr.millianlmx.tabatago ([6cc2530](6cc25300d1))
* **ci:** add -derivedDataPath and target find to build/derived — compatible with -scheme ([3ca3e54](3ca3e54837))
* **ci:** add mkdir -p build to Package IPA step ([d614e72](d614e72031))
* **ci:** add sudo back to xcode-select — must be run as root ([595140e](595140e2f9))
* **ci:** checkout PR branch instead of main ([5079de0](5079de0fb1))
* **ci:** clean SPM caches and DerivedData on CI runner to remove stale PostHog resolution ([00e02b7](00e02b7166))
* **ci:** clean SPM/Xcode caches agressivement pour éviter les segfaults linker après changement de bundle ID ([7c50282](7c50282284))
* **ci:** correct derived data path in install step ([7d9a89e](7d9a89e637))
* **ci:** disable code signing — Mac runner has no Apple Developer account ([4b75485](4b7548501a))
* **ci:** eliminate pipeline bug preventing ios-deploy fallback ([b956e51](b956e5189b))
* **ci:** isolate SPM and DerivedData to prevent cache corruption ([f14186a](f14186aeab))
* **ci:** keep SPM cache — remove aggressive clean that forces RevenueCat re-clone ([069b7d4](069b7d417f))
* **ci:** remove -derivedDataPath and -clonedSourcePackagesDirPath incompatible with -target ([8ce3833](8ce38332b8))
* **ci:** remove -derivedDataPath from resolve step (requires -scheme) ([abf926e](abf926e4bc))
* **ci:** remove grep -v DerivedData from Package IPA step — app builds to DerivedData ([4e2e807](4e2e807fd9))
* **ci:** remove stale .xcodeproj from git, nuke before xcodegen to prevent PostHog ghost dependency ([c45e85e](c45e85edb6))
* **ci:** remove sudo from docker install — Gitea ubuntu runner is root without sudo ([46d9566](46d95661ce))
* **ci:** remove sudo from Setup Xcode step to prevent password prompt hang ([9739a73](9739a739f5))
* **ci:** restore automatic code signing — Apple account now configured on runner ([63fbae3](63fbae3698))
* **ci:** revert build command from -scheme TabataGo to -target TabataGo ([3202ea9](3202ea9b5b)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* **ci:** switch from -target to -scheme for Xcode 26 SPM module path resolution ([b48178f](b48178f167))
* **ci:** switch from Firebase OTA to Xcode WiFi/USB direct deploy ([97b61d3](97b61d3afb))
* **ci:** switch to --token auth and add --project flag for Firebase deploy ([09e77b4](09e77b477c))
* **ci:** use npx firebase to avoid PATH issues with npm global install ([5c73d6b](5c73d6bf29))
* **ci:** use xcodebuild -version instead of xcode-select, clean only runner caches ([2aa312a](2aa312ab61))
* **ci:** wrap deploy commands in if blocks for -e protection ([b57584f](b57584f890))
* clean SPM cache, disable explicit modules ([dde01ce](dde01ce957))
* complete rewrite — add git clone checkout, install node+npm, fix all paths ([1b3ce24](1b3ce24fc9))
* dismiss paywall and sync premium state after successful purchase ([9f15ae2](9f15ae2d79))
* Dynamic Island, Live Activity errors, background audio stop, CFBundleVersion ([40ffb04](40ffb04f31))
* gitea→github context, add ios-deploy, remove -k ([7a02a89](7a02a8949e))
* kill stale Package.resolved, pin PostHog exact, add module emission flags ([7fb4419](7fb44198e7))
* Live Activity concurrency and state observation ([71de3c0](71de3c0aa7))
* Live Activity persists after workout cancel/background ([e42c121](e42c1217db))
* move #if outside array literal in TabataGoComplication.supportedFamilies ([a382ad6](a382ad62dc)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* move Live Activity ownership to ViewModel, fix timer-at-0 and background freeze ([c715c79](c715c797f9))
* permanently fix PostHog module emission error ([5817710](58177102a4)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* remove actions/checkout@v4 (no Node.js on Mac runner) ([8f3979c](8f3979c8a0))
* remove arch -arm64 from xcodegen (runner already handles it) ([7464357](7464357787))
* remove info.properties from widget target — use on-disk Info.plist with NSSupportsLiveActivities ([8073614](807361449b))
* remove PostHog from project.yml — EmitSwiftModule crashes on Xcode 26 ([afc161c](afc161c8ce))
* remove remaining force-unwrap in widgetURL (AGENTS.md §7) ([ee01380](ee01380be8))
* rename GITEA_TOKEN to PR_API_TOKEN (GITEA_ prefix reserved) ([e43f197](e43f197139))
* replace brew with npm for firebase-tools, use arch -arm64 for xcodegen ([1d4d43f](1d4d43f3f2))
* resolve all 228 TypeScript errors across the project ([4458044](4458044d0e))
* resolve redirect loop after login ([d2babbe](d2babbeee3))
* resolve trainers page issues and add seed data ([42d9b26](42d9b2671b))
* restore UIBackgroundModes audio for background playback ([316dab2](316dab2f9c))
* scope shared model sources to TabataGoWidget only ([210c254](210c254298))
* support both EXPO_PUBLIC and NEXT_PUBLIC env vars ([4c5bcc4](4c5bcc41c5))
* **test:** resolve unhandled rejection in sidebar logout error test ([20f0189](20f01899df))
* tolerate brew post-install warning exit code ([96654d6](96654d61c7))
* update PostHog to 3.62.0 to fix SwiftDriver compilation error ([5b67aea](5b67aea7ce))
* update workflow references to .github/ and gitignore xcuserstate ([560b056](560b056dc9))
* use canImport(WatchKit) guard for accessoryCorner, use -scheme in CI build ([60ac624](60ac624487)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if) [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if) [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* use createBrowserClient for proper cookie-based auth ([554ad2a](554ad2a352))
* use full brew path with arch -arm64 for Rosetta runner ([f5a777a](f5a777aee8))
* use full path for npm ([da62d6f](da62d6fa58))
* use xcodebuild build + manual ipa packaging (no scheme needed) ([a48ee00](a48ee0007a))
* **widget:** add NSExtension to info.properties for xcodegen compat ([309d67c](309d67c88a))
* wrap accessoryCorner in #if os(watchOS) for Xcode 26 compat ([a6ea4ca](a6ea4ca904)), closes [#if](https://gitea.1000co.fr/millianlmx/tabatago/issues/if)
* **youtube-worker:** sync package-lock.json with @google/genai dep and harden Dockerfile ([a4c6a12](a4c6a123a0))

### Features

* 5 tab screens wired to centralized data layer ([99d8fba](99d8fba852))
* add admin-web Docker build & push workflow with semantic-release ([0166398](0166398ae1))
* add dependencies for i18n, notifications, purchases, and analytics ([6a94d54](6a94d545f2))
* add form input components with tests ([3df7dd4](3df7dd4a47))
* add media upload component with comprehensive tests ([6adf709](6adf709dce))
* add reusable UI components with tests ([71e9a9b](71e9a9bdb5))
* add shared card components ([001b376](001b376fc0))
* add storage utilities for file management ([e2e9988](e2e99887ac))
* add success toasts for create operations and login ([fc43f73](fc43f73b82))
* add workout form component with advanced test mocking ([9dd1a4f](9dd1a4fe7c))
* add workout management pages ([bd14922](bd14922efa))
* **admin-web, functions:** overhaul music library and add AI genre classification ([edcd857](edcd857c70))
* Apple Watch app + Paywall + Privacy Policy + rebranding ([2ad7ae3](2ad7ae3a34))
* category/collection detail screens + Inter font loading ([2d24831](2d24831f8e))
* close v1 feature gaps — freemium gating, video/audio infrastructure, EAS build config ([a042c34](a042c348c1))
* data layer with types, 50 workouts, and Zustand stores ([5477ecb](5477ecb852))
* design system v2 with liquid glass aesthetic ([511e207](511e207762))
* Dynamic Island pause state, Apple-aligned spacing, and UI polish ([95f34e6](95f34e6471))
* explore tab, React Query data layer, programs, sync, analytics, testing infrastructure ([cd065d0](cd065d07c3))
* extend user profile with onboarding data model ([b600833](b60083341e))
* i18n infrastructure with 4-locale support (en, fr, es, de) ([d6bc7f5](d6bc7f5a4c))
* i18n locale files for all screens (en, fr, es, de) ([e59c87f](e59c87fd1b))
* **i18n:** complete internationalization for iOS + watchOS across all views ([0f5b7b9](0f5b7b9e18))
* implement beautiful toast notifications with Sonner ([3d026b6](3d026b68ee))
* implement full authentication system with middleware protection ([e0057e1](e0057e18e0))
* implement React Query for Supabase data fetching ([b1741e9](b1741e965c))
* integrate theme and i18n across all screens ([f807980](f80798069b))
* Live Activity accessibility and supplemental families (small/medium) ([fe005ee](fe005ee7f3))
* migrate icons to SF Symbols, refactor explore tab, add collections/programs data layer ([b833198](b833198e9d))
* move HealthKit permission to onboarding, remove HR write ([310124a](310124ad63))
* notification, purchase, and analytics services ([540bb01](540bb015c7))
* onboarding flow (6 screens) + audio engine + design system ([fa189fe](fa189fe72e))
* onboarding flow with staggered-reveal wow screen ([aa75afb](aa75afb1b7))
* production-grade Live Activity with type-safe phases, decomposed views, previews, and alert transitions ([dc3ff15](dc3ff15e81)), closes [#Preview](https://gitea.1000co.fr/millianlmx/tabatago/issues/Preview) [#Preview](https://gitea.1000co.fr/millianlmx/tabatago/issues/Preview)
* redesign Dynamic Island with phase-driven UI and animations ([c152c22](c152c22ffb))
* redesign player with Dynamic Island, compact timer, and fix Live Activity timer drift ([b0d364e](b0d364eca2))
* replace native confirm() dialogs with custom delete confirmation dialogs ([b177656](b177656efc))
* shared components, hooks, and audio engine ([13faf21](13faf21b8d))
* switch to Firebase OTA deployment, drop ios-deploy ([9b06c0a](9b06c0ae5c))
* system light/dark theme infrastructure ([f17125e](f17125e231))
* timer engine + full-screen timer UI ([31bdb15](31bdb1586f))
* update Home screen to use React Query with loading states ([1973241](197324188c))
* update mobile app screens ([8c8dbeb](8c8dbebd17))
* workout flow — detail, player, and complete screens ([b0521de](b0521ded5a))
* YouTube music download system with admin dashboard ([3d8d9ef](3d8d9efd70))
2026-07-19 14:11:00 +00:00
12 changed files with 272 additions and 176 deletions

View File

@@ -1,24 +1,35 @@
# =============================================================================
# TabataGo — PR → Build → WiFi/USB Direct Deploy → Wait LGTM → Merge
# TabataGo — PR → Build → devicectl Deploy → Wait LGTM → Merge
# =============================================================================
name: PR → Build → WiFi/USB Deploy → LGTM
name: PR → Build → devicectl Deploy → LGTM
on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Only run the iOS build+deploy when the PR actually touches the Swift app
# or this workflow itself. Docs-only / backend-only / admin-web-only PRs
# skip the runner entirely (no point rebuilding an unchanged .app).
paths:
- 'tabatago-swift/**'
- '.github/workflows/pr-iphone-deploy.yml'
permissions:
contents: write
pull-requests: write
# Every mutation (comments, merge) goes through the Gitea API with PR_API_TOKEN,
# never the runner's native GITHUB_TOKEN — so a `permissions:` block would be
# a no-op here. Intentionally omitted.
concurrency:
# Per-PR grouping: superseded pushes cancel the previous pipeline, different
# PRs still run in parallel. Prevents double-merges and wasted runner time.
group: pr-iphone-deploy-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
build-deploy:
name: Build & Deploy to iPhone (WiFi/USB)
name: Build & Deploy to iPhone (devicectl)
runs-on: macos
timeout-minutes: 20
timeout-minutes: 30
steps:
- name: Checkout
@@ -42,9 +53,11 @@ jobs:
# Act runner caches
rm -rf ~/.cache/act/*/hostexecutor/tabatago-swift/build 2>/dev/null || true
# SPM caches are KEPT between runs — RevenueCat is 1.10 GiB and takes
# SPM cache is KEPT between runs — RevenueCat is ~1.10 GiB and takes
# 5+ minutes to clone; re-cloning on every CI run would waste time
# and cause random failures on the act_runner sandbox.
# and cause random failures on the act_runner sandbox. It lives at
# build/spm-cache (see -clonedSourcePackagesDirPath in the Build step)
# so NEVER `rm -rf build/` — only delete build/derived.
# Xcode DerivedData — suppression COMPLÈTE (le bundle ID a changé,
# l'ancien dossier TabataGo-* peut être ignoré par le nouveau build)
@@ -52,10 +65,12 @@ jobs:
# Module cache Xcode — des .pcm stale peuvent segfault le linker
rm -rf ~/Library/Developer/Xcode/DerivedData/ModuleCache.noindex 2>/dev/null || true
rm -rf ~/Library/Caches/com.apple.dt.Xcode
# NOTE: do NOT `rm -rf ~/Library/Caches/com.apple.dt.Xcode` — that is the
# shared global cache; wiping it slows every subsequent build.
# Build artifacts locaux (utilisés par le step "Build app")
rm -rf build/
# Build artifacts locaux — sparing build/spm-cache (see above).
rm -rf build/derived
rm -rf build/*.log 2>/dev/null || true
rm -rf .build
rm -rf tabatago-swift/.build
rm -rf tabatago-swift/TabataGo.xcodeproj
@@ -65,11 +80,45 @@ jobs:
find . -name "Package.resolved" -delete 2>/dev/null || true
find . -name ".package.resolved" -delete 2>/dev/null || true
echo "✅ Caches nettoyés"
echo "✅ Caches nettoyés (SPM cache préservé)"
- name: Install tools (xcodegen, node, ios-deploy)
- name: Install tools (xcodegen)
run: |
brew install xcodegen node ios-deploy || true
# The act_runner shell can land under Rosetta 2 on Apple Silicon;
# Homebrew lives in /opt/homebrew (ARM prefix), so run brew natively.
# (Brew refuses to install/upgrade under Rosetta in the ARM prefix.)
# Only xcodegen is needed — node is NOT used by any step in this
# workflow, and installing it triggers brew's "installed dependents
# check" which tries to rebuild unrelated formulae (e.g. gemini-cli)
# and fails the build.
arch -arm64 brew install xcodegen
- name: Write Secrets.xcconfig from Gitea secrets
env:
SUPABASE_URL: ${{ secrets.SUPABASE_URL }}
SUPABASE_ANON_KEY: ${{ secrets.SUPABASE_ANON_KEY }}
REVENUECAT_API_KEY: ${{ secrets.REVENUECAT_API_KEY }}
POSTHOG_API_KEY: ${{ secrets.POSTHOG_API_KEY }}
run: |
# Preflight: fail loudly if a secret is missing, rather than producing
# an .app that silently can't reach Supabase/RevenueCat at runtime.
for v in SUPABASE_URL SUPABASE_ANON_KEY REVENUECAT_API_KEY; do
val="$(printenv "$v")"
if [ -z "$val" ]; then
echo "❌ Secret missing: $v. Add it to Gitea repo secrets."
exit 1
fi
done
# POSTHOG_API_KEY may legitimately be empty (PostHog not yet wired).
# NOTE: heredoc body MUST start at column 0 — xcconfig keys are
# whitespace-sensitive, leading spaces would break the build setting.
cat > tabatago-swift/Config/Secrets.xcconfig <<EOF
SUPABASE_URL = ${SUPABASE_URL}
SUPABASE_ANON_KEY = ${SUPABASE_ANON_KEY}
REVENUECAT_API_KEY = ${REVENUECAT_API_KEY}
POSTHOG_API_KEY = ${POSTHOG_API_KEY}
EOF
echo "✅ Secrets.xcconfig written (values redacted)"
- name: Generate Xcode project
run: |
@@ -99,27 +148,20 @@ jobs:
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM=2MJF39L8VY
- name: Install to iPhone (WiFi → USB fallback)
- name: Install to iPhone (devicectl)
run: |
UDID="00008120-000925CE3672201E"
APP=$(find build/derived -name 'TabataGo.app' -type d | head -1)
# 1. Try WiFi first (Xcode 15+ devicectl network discovery)
echo "📱 Trying WiFi install via devicectl..."
if xcrun devicectl device install app --device "$UDID" "$APP" > /tmp/devicectl.log 2>&1; then
echo "✅ Installed via WiFi"
exit 0
fi
tail -10 /tmp/devicectl.log
# 2. Fallback to USB via ios-deploy
echo "🔌 WiFi failed, trying USB..."
if ios-deploy --bundle "$APP" --id "$UDID" --justlaunch; then
echo "✅ Installed via USB"
# devicectl handles both WiFi (network) and wired (USB-C/Thunderbolt)
# discovery natively — no separate USB-fallback tool needed.
echo "📱 Installing via devicectl..."
if xcrun devicectl device install app --device "$UDID" "$APP"; then
echo "✅ Installed via devicectl"
exit 0
fi
echo "❌ Install failed (both WiFi and USB)"
echo "❌ devicectl install failed"
exit 1
@@ -133,7 +175,7 @@ jobs:
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (USB/WiFi).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
-d "{\"body\":\"## 📱 Prêt à tester !\\n\\nL'app est déployée sur l'iPhone (devicectl).\\n\\n- Teste les changements\\n- Reply **LGTM** pour merger\\n- Reply **KO** pour bloquer\"}" \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR}/comments"
wait-approval:
@@ -157,29 +199,31 @@ jobs:
echo "⏳ Attente LGTM sur PR #${PR}..."
LAST_ID=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments" | \
python3 -c "import json,sys; cs=json.load(sys.stdin); print(cs[-1]['id'] if cs else 0)" 2>/dev/null || echo 0)
# Re-fetch ALL comments each cycle (not since_id) so we also catch
# edits — e.g. a reviewer changing "KO" → "LGTM". Negligible cost for
# PRs with <50 comments.
while [ $TRIES -lt $MAX ]; do
sleep 30
TRIES=$((TRIES + 1))
COMMENTS=$(curl -s -H "Authorization: token ${GT_TOKEN}" \
"${API}/issues/${PR}/comments?since_id=${LAST_ID}")
"${API}/issues/${PR}/comments?limit=50&page=1")
if echo "$COMMENTS" | grep -qi '"body": *"LGTM"'; then
echo "✅ LGTM reçu ! Merge..."
# Match LGTM/KO anywhere in the body (with a word boundary so "KOM"
# or "LGTMX" don't trigger). Case-insensitive.
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bLGTM\b'; then
echo "✅ LGTM reçu ! Squash-merge..."
curl -s -X POST \
-H "Authorization: token ${GT_TOKEN}" \
-H "Content-Type: application/json" \
-d "{\"Do\":\"merge\"}" \
-d "{\"Do\":\"squash\"}" \
"${API}/pulls/${PR}/merge"
echo "✅ Mergée."
echo "✅ Mergée (squash)."
exit 0
fi
if echo "$COMMENTS" | grep -qi '"body": *"KO"'; then
if echo "$COMMENTS" | grep -qiE '"body":[[:space:]]*"[^"]*\bKO\b'; then
echo "❌ KO reçu. Bloquée."
exit 1
fi

View File

@@ -152,34 +152,43 @@ Complications : `TabataGoComplication`.
### Workflow : `.github/workflows/pr-iphone-deploy.yml`
**Trigger** : PR ouverte/synchronize/reopened sur `main`.
**Runner** : label `macos` (auto-hébergé).
**Runner** : label `macos` (auto-hébergé). `timeout-minutes: 30` (build-deploy), `120` (wait-approval).
**Concurrency** : `group: pr-iphone-deploy-<PR#>`, `cancel-in-progress: true` → un push sur une PR existante annule le pipeline précédent (pas de double-merge, pas de runner gaspillé). Différentes PRs tournent en parallèle.
**Déroulé** :
1. **Checkout** (shallow, branche head) via `PR_API_TOKEN`.
2. **Setup PATH** : `echo "/opt/homebrew/bin" >> $GITHUB_PATH` — requis car Rosetta ne le voit pas.
3. **Clean ciblé** :
- Supprime build artifacts, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
- **GARDE le cache SPM** (`../build/spm-cache`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox.
4. **Install tools** : `brew install xcodegen node ios-deploy`.
5. **xcodegen generate**`xcodebuild -resolvePackageDependencies`**build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`).
6. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `devicectl` WiFi d'abord, fallback `ios-deploy` USB.
7. **Post comment** "Prêt à tester" sur la PR.
8. **Job `wait-approval`** : poll (30s, max 240 = 2h) les commentaires de la PR. `LGTM` → auto-merge. `KO` → blocage. Timeout → fail.
- Supprime `build/derived`, `TabataGo.xcodeproj`, `Package.resolved`, DerivedData, ModuleCache.
- **GARDE le cache SPM** (`build/spm-cache` — c'est le chemin passé à `-clonedSourcePackagesDirPath`) — RevenueCat ≈ 1.1 GiB, re-cloner = 5+ min et échecs aléatoires du sandbox. **Ne jamais faire `rm -rf build/`** : ça détruirait ce cache. Le clean step ne touche que `build/derived`.
- Ne touche **pas** à `~/Library/Caches/com.apple.dt.Xcode` (cache Xcode global — le nuke ralentit tous les builds suivants).
4. **Install tools** : `arch -arm64 brew install xcodegen` (sans `|| true` — si brew down, on fail ici, pas deux steps plus loin). `arch -arm64` requis car le shell act_runner tourne sous Rosetta 2 sur Apple Silicon, mais Homebrew est dans le prefix ARM `/opt/homebrew` — sans ça, brew refuse d'installer/upgrader. Seul xcodegen est nécessaire : **node n'est pas utilisé** par ce workflow, et l'installer déclenche le "installed dependents check" de brew qui essaie de rebuilder des formulae sans rapport (ex. `gemini-cli`) et fail le build.
5. **Write `Config/Secrets.xcconfig`** depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`). Preflight : fail loud si un secret requis manque. Le fichier est gitigné (règle #9) ; CI l'écrase à chaque run. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc., et `project.yml` wire le xcconfig en Debug+Release — aucun autre change­ment nécessaire côté build.
6. **xcodegen generate**`xcodebuild -resolvePackageDependencies`**build** (`-scheme TabataGo`, Debug, auto-provisioning, team `2MJF39L8VY`). Le scheme build les 4 targets : `TabataGo` (app iOS), `TabataGoWidget` (widget iOS, vrai target — ne pas confondre avec `TabataGoWatchWidget` qui est watchOS), `TabataGoWatch` (app watchOS), `TabataGoWatchWidget` (complication watchOS).
7. **Deploy iPhone** UDID `00008120-000925CE3672201E` : `xcrun devicectl device install app` uniquement. `devicectl` gère nativement la découverte WiFi (réseau) et filaire (USB-C/Thunderbolt) — pas de fallback `ios-deploy`.
8. **Post comment** "Prêt à tester" sur la PR.
9. **Job `wait-approval`** : poll (30s, max 240 = 2h). **Re-fetche TOUS les comments** chaque cycle (pas `since_id`) pour attraper aussi les edits (un reviewer passant de "KO" à "LGTM"). Match LGTM/KO n'importe où dans le body (regex `\bLGTM\b` / `\bKO\b`, case-insensitive) — "Tested, LGTM!" compte ; "KOM" ne compte pas. `LGTM`**squash-merge** (`{"Do":"squash"}`). `KO` → blocage. Timeout → fail.
### Secrets
| Secret | Usage |
|--------|-------|
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`** |
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Injectés via `Config/Secrets.xcconfig` → Info.plist |
| `PR_API_TOKEN` | Checkout + API Gitea (comment, merge). **JAMAIS `GITEA_TOKEN`/`GITHUB_TOKEN`**. Tout passe par l'API Gitea avec ce token — un bloc `permissions:` natif serait no-op, c'est pourquoi il n'y en a pas. |
| `SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY` | Un step CI écrit `Config/Secrets.xcconfig` depuis ces 4 secrets **avant** `xcodegen generate`. `Info.plist` lit les valeurs via `$(SUPABASE_URL)` etc. ; `project.yml` wire le xcconfig en Debug+Release. `POSTHOG_API_KEY` peut être vide (PostHog pas encore câblé en SPM). |
> ⚠️ **Historique** : `Config/Secrets.xcconfig` a été committé par erreur dans le passé (avant la règle gitignore) avec des valeurs réelles. Le fichier est maintenant gitigné et écrasé par CI, mais l'historique git contient encore l'ancienne version — **rotation des clés Supabase/RevenueCat recommandée côté ops**.
### ⚠️ Pitfalls CI
- `/opt/homebrew/bin` **hors PATH par défaut** sous Rosetta → toujours l'ajouter.
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer).
- **Ne jamais `rm -rf` le cache SPM** dans le CI (c'est l'inverse des build artifacts).
- **xcodegen 2.45.4 ne génère pas de schemes auto** → scheme `TabataGo` défini explicitement dans `project.yml` (ne pas le supprimer). Il liste **4 targets** : `TabataGo`, `TabataGoWidget`, `TabataGoWatch`, `TabataGoWatchWidget`.
- **Ne jamais `rm -rf build/` ni `rm -rf build/spm-cache`** dans le CI — RevenueCat y vit. Le clean step ne touche que `build/derived`.
- `SWIFT_ENABLE_EXPLICIT_MODULES=NO` au build — requis sinon segfault linker sur `.pcm` stale.
- `-skipPackagePluginValidation -allowProvisioningUpdates`.
- **Concurrency** : le bloc `concurrency:` est volontaire — ne pas le retirer, sinon les pushes successifs empilent des pipelines et tentent des double-merges.
- **LGTM/KO regex** : matche n'importe où dans le body avec `\b...\b` (word boundary). Ne pas revenir à un `grep '"body": *"LGTM"'` ancré — il raterait "Tested, LGTM!".
- **Squash-merge** (`{"Do":"squash"}`) — pas merge commit ni rebase. Garde l'historique `main` linéaire.
- **Comment edits** : `wait-approval` re-fetche tous les comments chaque cycle (pas `since_id`) pour attraper les edits.
---
@@ -219,7 +228,7 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
6. **Navigation = TabView + sheet/fullScreenCover.** **Pas de `NavigationStack`.**
7. **Tout en français** côté user-facing (`Localizable.xcstrings` / `L10n`). Codes/types restent en anglais.
8. **XcodeGen.** Éditer `project.yml`, pas `.xcodeproj`. Régénérer avec `xcodegen generate`.
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`.
9. **Secrets.** Jamais committer `Config/Secrets.xcconfig`. Template = `.example`. En CI, un step écrit ce fichier depuis les secrets Gitea (`SUPABASE_URL`, `SUPABASE_ANON_KEY`, `REVENUECAT_API_KEY`, `POSTHOG_API_KEY`) **avant** `xcodegen generate` ; `Info.plist` lit les valeurs via `$(VAR)` et `project.yml` wire le xcconfig en Debug+Release.
10. **Lazy d'abord.** Avant d'ajouter une dépendance : stdlib Apple, puis ce qui existe déjà dans le repo, puis SPM déjà installé. PostHog n'est pas installé — ne pas l'ajouter sans justification.
---
@@ -234,9 +243,14 @@ Node.js (`server.js`, `package.json`, `Dockerfile`). Télécharge l'audio de pla
| `@ObservableObject` / `@Published` | `@Observable` |
| Force-unwrap `!` | `guard let` |
| Strings UI en anglais | Françaises dans `L10n`/`Localizable.xcstrings` |
| `rm -rf` le cache SPM en CI | Garder `build/spm-cache` |
| `rm -rf build/` ou `rm -rf build/spm-cache` en CI | Garder `build/spm-cache` ; ne cleaner que `build/derived` |
| `rm -rf ~/Library/Caches/com.apple.dt.Xcode` | Laisser le cache Xcode global intact (sinon tous les builds ralentissent) |
| Utiliser `GITEA_TOKEN` en CI | `PR_API_TOKEN` uniquement |
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas |
| Compter sur un bloc `permissions:` natif Gitea Actions | Tout passe par l'API Gitea avec `PR_API_TOKEN` ; le bloc natif serait no-op |
| Retirer le bloc `concurrency:` | Permet double-merge et pipelines empilés |
| Ancre le grep LGTM au début du body (`'"body": *"LGTM"'`) | Regex `\bLGTM\b` n'importe où dans le body (attrape "Tested, LGTM!") |
| `{"Do":"merge"}` (merge commit) | `{"Do":"squash"}` pour un `main` linéaire |
| Supprimer le scheme explicite dans `project.yml` | xcodegen 2.45.4 n'en crée pas — scheme doit lister `TabataGo`/`TabataGoWidget`/`TabataGoWatch`/`TabataGoWatchWidget` |
| Compter sur `admin-web/` pour la app iOS | Dashboard admin séparé, communique via Supabase uniquement |
| Recréer `programs`/`program_workouts` | Remplacés par `workout_programs`/`program_tabatas` (migration 005) |

View File

@@ -1,6 +1,6 @@
{
"name": "my-app",
"version": "0.1.0",
"version": "1.0.1",
"private": true,
"scripts": {
"dev": "next dev",

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "full.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,19 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(178.668,1429.023,-572.914,71.631,128.681,37.656)">
<stop offset="0" stop-color="#ff6b35"/>
<stop offset=".275" stop-color="#cc5dd1"/>
<stop offset=".345" stop-color="#a657cd"/>
<stop offset=".503" stop-color="#8050c9"/>
<stop offset=".627" stop-color="#a657cd"/>
<stop offset=".714" stop-color="#cc5dd1"/>
<stop offset="1" stop-color="#ff6b35"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
</style>
<g id="Layer 1">
<path class="s0" d="m149.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68 1.43 16.48 5.24 32.66 11.29 48.06 10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63-7.1-23.21-10.35-47.43-9.61-71.69 1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72zm79.43-519.52c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-16.14 26.38-18.51 52.09l-2.19-0.67c0.76-6.48 0.66-8.78 0.37-15.3-10.91-1.12-22.08 2.71-33 2.71-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-3.77 14.36-7.36 21.63 0.14-13.93 0.79-28.35-4.64-41.58-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23zm-213.96 588.23c1.83 2.25 3.56 3.43 4.04 6.5l-0.15 0.37c0.67-0.12 3.42-0.65 4.11-0.48l0.78 0.28c-0.14 0.74-0.64 1.26-1.26 1.73 11.75 5.35 23.86-1.69 35.49 2.76 2.43 0.93 11.87-0.55 18.16 3.27 0.58 0.35-1.16 1.99-1.07 2.25-10.21 21.3-28.3 54.35-20.7 77.65 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-3.84-17.11-4.32-24.04zm410.44 25.02c2.06-0.34 3.75-0.95 5.81-1.46l-2.41-1c10.21-1.59 18.92 0.05 29.06-0.16 6.49-0.14 14.59-3.22 21.22-4.13l-1.07 5.11c-2.79 19.29-4.66 36.49-4.32 56.19 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-11.35-36.01-19.9-54.48z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "lower.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,33 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(118.66,949.065,-461.315,57.678,134.904,524.495)">
<stop offset="0" stop-color="#262bdf"/>
<stop offset="1" stop-color="#ff004a"/>
</linearGradient>
<linearGradient id="g2" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(115.853,-95.706,134.182,162.429,-1.643,770.379)">
<stop offset="0" stop-color="#010319"/>
<stop offset="1" stop-color="#432640"/>
</linearGradient>
<linearGradient id="g3" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(67.601,0,0,205.585,413.199,632.988)">
<stop offset="0" stop-color="#432640"/>
<stop offset=".618" stop-color="#221320"/>
<stop offset="1" stop-color="#000000"/>
</linearGradient>
</defs>
<style>
.s0 { fill: url(#g1) }
.s1 { fill: #432640 }
.s2 { fill: #420a44 }
.s3 { fill: url(#g2) }
.s4 { fill: url(#g3) }
</style>
<g id="Layer 1">
<path class="s0" d="m151.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68 1.43 16.48 5.24 32.66 11.29 48.06 10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63-7.1-23.21-10.35-47.43-9.61-71.69 1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72z"/>
<path class="s1" d="m228.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-15.09 26.03-17.46 51.74l-1.98-0.54c0.76-6.48 0.84-8.58 0.55-15.1-10.91-1.12-23.52 2.73-34.44 2.73-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-2.89 15.84-6.48 23.11 0.14-13.93-0.09-29.83-5.52-43.06-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23z"/>
<path class="s2" d="m137.49 486.15l0.83 0.4c3.58 3.86 4.57 6.2 7.04 10.78 2.53 1.37 29.78-6.88 35.79-7.66 31.97-4.15 57.07-8.91 89.05-5.22 14.5 1.66 73.6-6.97 83.4 4.36 3.86-2.96 2.27-7.93 6.54-10.82l1.66 0.39c-11.83 23.62-11.9 26.19-15.71 50.68l-3.13-1.07c0.76-6.49 0.74-6.53 0.45-13.06-10.93-1.13-22.64 2.8-33.58 2.8-45.39-0.02-91.69-3.51-136.7 3.54-4.31 0.68-14.32-0.91-15.42 3.51-3.59 14.44-3.36 14.33-6.95 21.62 0.14-13.96 0.37-28.35-5.06-41.6-2.2-5.37-5.47-11.9-7.36-17.1zm-125.64 103.79c-0.05-0.1 4.68 12.22 3.52 10.71 0.38 0 7.47-4.2 11.74-1.94 0.09 0.04 7.38 0.95 7.68 1.41 9.61-0.67 19.35-1.03 28.95 0.48 5 0.8 12.28-1.42 16.12 1.4 2.04 1.69 3.07-2.07 4.4 1.27l0.47 0.38c-0.52 0.44 2.95-9.32 5.93-12.26-2.3 9.24-12.2 32.91-16.89 42.03-0.36-0.98-0.86-2.66-0.89-3.58q0.43-0.51 0.85-1.02c-2.02-1.71-14.65-1.9-17.06-2.37-12.24-2.4-23.77 2.59-35.54-2.77 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.93 0.69-4.6 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.92-1.64-3.12-2.88-22.79-3.01-26.9z"/>
<path class="s1" d="m52.76 380.21c1.37 2.56 1.35 8.38 1.48 11.3l-1.27 1.58c-4.76 5.75 0.61 8.2-4.45 13.72-0.83 0.9-0.88 2.54-0.98 3.59-1.84 2.76-0.81 6.37-1.73 8.77-1.8 4.72 1.73 8.02-0.56 11.78-2.34 3.4 1.25 7.72-0.94 10.32q-0.94 0.84-1.89 1.67-0.19 0.17-0.38 0.33c-0.53-17.02 1.6-48.35 10.72-63.06z"/>
<path class="s1" d="m146.83 300c0.83-3.28 2.54-4.24 7.14-4.06 4.96 0.2 3.67-1.38 6.81-1.97 0.4 4.07 5.05 0.81 7.46 2.52 0.78-0.54-11.99 4.75-12.8 5.04-6.77 2.38-12.92 3.93-19.38 7.33-5.8 2.87-2.93 1.63-5.27-0.28-1.44 0.13-1.59 4.98-3.34 3.63-0.26-0.2-2.01-2.29-2.37-2.65 3.82-15.23-2.85 11.41 0 2.23-1.07 3.47-13.54 10.35-17.46 12.26-2.93-2.2 3.96-3.51 4.37-5.03-0.22-0.65-0.83-0.83-0.67-1.57 1.13-1.24 2.95-0.34 3.64-1.95 0.3-0.71-0.28-1.34 0.35-2.22 1.65-0.74 1.43 0.22 3.59-1.15 5.05-4.04 4.65-6.42 9.73-9.97 1.09-0.75 8.74-0.94 8.79-0.92 0.13 0.53 0 1.21 0.24 1.66 2.96 1.13 1.02-5.73 4.18-4.37 0.47 1.14-0.53 2.33-0.17 3.44 3.23-1.43 1.84-1.39 5.16-1.97z"/>
<path class="s3" d="m14.38 614.82c1.83 2.25 3.81 2.64 4.29 5.71l-0.01 0.02c0.67-0.12 3.9-0.98 4.59-0.81l0.19 0.2c-0.14 0.74-0.8 1.04-1.42 1.51 11.75 5.35 23.27 0.37 35.49 2.76 2.4 0.47 15.01 0.66 17.03 2.37q-0.42 0.51-0.85 1.02c0.03 0.92 0.82 2.62 1.18 3.6-10.21 21.3-28.57 52.88-20.97 76.18 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-3.96-15.34-4.44-22.27z"/>
<path class="s4" d="m425 638.26c0.01-0.17-0.43-0.84-0.56-1.11l-0.61-1.1c10.8-2.24 22.84-0.22 32.98-0.43 6.49-0.14 12.95-1.72 19.58-2.63l1.42 1.71c-2.79 19.29-4.92 38.22-4.58 57.92 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-11.29-35.82-19.84-54.29z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "upper.svg",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"preserves-vector-representation" : true
}
}

View File

@@ -0,0 +1,35 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 495 1461" width="495" height="1461">
<defs>
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(79.084,632.495,-470.376,58.814,180.959,30.752)">
<stop offset="0" stop-color="#ff0031"/>
<stop offset="1" stop-color="#ff9f0a"/>
</linearGradient>
<linearGradient id="g2" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(115.853,-95.706,133.729,161.88,-1.421,770.649)">
<stop offset="0" stop-color="#190a01"/>
<stop offset="1" stop-color="#442a27"/>
</linearGradient>
<linearGradient id="g3" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(135.335,-111.799,103.573,125.376,377.747,785.651)">
<stop offset="0" stop-color="#442a27"/>
<stop offset="1" stop-color="#190a01"/>
</linearGradient>
</defs>
<style>
.s0 { fill: #442a27 }
.s1 { fill: url(#g1) }
.s2 { fill: #b27866 }
.s3 { fill: #fe0128 }
.s4 { fill: #ff9f0a }
.s5 { fill: url(#g2) }
.s6 { fill: url(#g3) }
</style>
<g id="Layer 1">
<path class="s0" d="m149.03 544.34c3.59-7.27-0.19-15.12 7.36-21.63 3.92-2.3 11.09-2.83 15.4-3.51 44.94-7.04 91.17-3.55 136.49-3.53 10.92 0 23.53-3.85 34.44-2.73 0.29 6.52 0.21 8.62-0.55 15.1l1.98 0.54c-0.19 10.19 0.74 18.24 1.72 28.16 4.72 29.55 15.11 54.57 22.13 83.56 3.69 15.25 0.25 49.47 3.91 59.74 17.91 50.16 30.14 101.44 29.47 155.05-0.24 21.26-3.62 42.37-10.03 62.65-9.96 31.18-22.38 45.91-19.61 79.68q0.54 6.17 1.52 12.29 0.98 6.12 2.39 12.15 1.42 6.03 3.27 11.94 1.84 5.91 4.11 11.68c10.62 27.24 29.51 58.1 28.02 88.22-2.55 51.69-18.34 101.56-27.01 152.81-7.33 43.37-5.74 87.73 34.56 114.99 12.04 8.15 23.8 17.44 37.37 23.36 8.86 3.65 20.16 1.23 25.52 11.62 0.12 6.51-11.3 10.06-16.94 10.17-38.13 0.75-78.38 0.62-116.36-0.07-12.03-0.22-18.15-14.26-14.45-22.95 1.69-10.01 10.18-25.03 10.62-33 3.01-53.65 1.91-113.08-15.15-164.51-7.62-22.97-22.39-39.17-24.76-64.01-4.36-37.82 3.93-74.64 9.04-111.51-6.48-12.98-11.74-23.79-15.37-38-7.22-29.23-5.52-56.92-11.65-86.72-6.19-30.1-16.42-56.79-24.98-85.83-5.37-18.22-13.09-56.73-13.41-75.86l-2.13-2.45c-1.42 20.22-5.31 51.68-11.23 70.93-12.01 39.43-30.84 84.8-32.77 126.47-1.84 39.64-5.04 56.11-22.62 92.02 6.01 35.03 14.58 75.52 9.45 111.04-3.29 22.78-16.2 41.16-24.26 62.2-13.16 34.38-16.78 77.17-17.34 113.84-0.26 16.75-1.22 35.85 1.36 52.32 2.12 13.5 16.56 34.31 9.9 47.51-1.3 2.58-3.47 5.5-6.34 6.37-4.53 1.38-9.47 2.1-14.19 2.41-13.51 0.87-114.42 1.81-120.26-2.21-2.57-1.78-4.91-4.83-4.78-8.1 0.02-0.62 0.21-1.22 0.37-1.81 1.47-5.48 8.4-6.26 13.02-7.07 20.23-3.54 38.54-18.08 54.46-30.54 19.62-15.36 29.84-38.78 32.02-63.37 1.68-18.86-1.76-38.54-5.08-57.1-7.72-43.25-21.12-86.38-24.7-130.18-3.43-42.06 14.35-65.14 28.23-101.9 5.69-15.1 8.98-31.36 10.51-47.4 2.79-29.35-11.78-53.53-20.03-80.63q-2.66-8.69-4.6-17.57-1.94-8.87-3.14-17.88-1.21-9.01-1.68-18.08-0.47-9.08-0.19-18.16c1.34-36.47 7.09-72.63 17.12-107.71 2.92-10.46 11.61-31.56 12.54-40.67 1.88-20.29-1.06-47.02 5.37-66.41 10.1-30.44 18.11-53.31 21.94-85.72z"/>
<path class="s1" d="m229.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9 20.08 17.43 23.2 41.99 19.84 66.29-1.05 7.63-0.85 8.56 5.85 10.63 1.3 1.4 2.95 3.1 2.22 5.42-4.18 13.37-8.51 31.09-17.55 41.8-2.27 2.69-6.69 1.3-8.05 7.08-11.41 48.56 19.04 64.83 58.08 78.29 36.4 9.7 71.89 11.72 93.52 48.05 11.63 19.54 14.83 50.36 9.42 72.21-1.55 8.52 9.91 89.43 1.67 130.98 4.95 8.12 9.09 14.57 12.9 23.34 18.89 34.96 15.91 75.2 8.06 112.62q-0.06 0.19-0.11 0.37l-0.91-2.59c-6.63 0.91-14.16 2.49-20.65 2.63-10.14 0.21-17.09-1.62-27.3-0.03l2.99 0.78c-2.06 0.51-4.5 1.19-6.56 1.53-7.52-11.89-17.63-36.39-21.52-49.49-5.19-17.46-4.07-44.93-0.68-62.88l1.28-10.65c-11.34-26.61-19.11-47.16-26.23-74.9-3.69 10.15-9.9 27.91-15.38 36.64-11.81 23.58-17.01 26.91-19.38 52.62l-1.99-1.31c0.76-6.48 0.72-8.7 0.43-15.22-10.91-1.12-21.47 2.74-32.39 2.74-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-5.57 14.61-9.16 21.88 0.14-13.93 2.59-28.6-2.84-41.83-2.19-5.36-6.52-11.7-8.41-16.89l-0.56-1.6c-6.86-11-14.64-31.32-19.55-43.55-3.95 21.45-16.92 53.79-25.63 73.85 2.28 17.84 7.52 61.35-0.73 77.33-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.65-3.12-2.87-22.76-3-26.85q-0.11-0.37-0.21-0.73c-1.78-31.25 10.69-61.99 26.32-86.97-3.25-20.68-4.17-36.75-3.67-57.55 1.23-16.98 11.03-63.02 9.34-65.62-6.42-9.85-6.43-35.25-4.59-46.11 0.18-9.32 3.97-22.85 8.31-31.07 31.5-59.72 109.32-33.15 147.52-82.33 11.03-14.2 9.09-34.13 6.05-50.94-13.96-3.76-16.46-20.24-21.19-31.43-2.11-8.16-8.45-18.14 3.1-21.54 1.9-0.56-0.89-26.55-0.64-30.57 0.52-8.64 2.73-17.08 6.51-24.86 8.45-17.15 22.07-27.28 39.85-33.23z"/>
<path class="s2" d="m137.42 484.27l1.61 0.27c3.58 3.85 4.56 6.19 7.03 10.76 2.53 1.37 29.73-6.87 35.73-7.65 31.93-4.14 56.99-8.89 88.92-5.21 14.47 1.66 74.45-6.2 84.24 5.11 3.85-2.95 2.1-8.27 6.37-11.16l2.29 0.45c-11.81 23.58-16.74 26.67-19.11 52.38l-2.66-1.06c0.76-6.48 1.14-8.44 0.85-14.96-10.91-1.12-21.49 2.47-32.41 2.47-45.32-0.02-91.55-3.51-136.49 3.53-4.31 0.68-11.48 1.21-15.4 3.51-7.55 6.51-5.64 15-9.23 22.27 0.14-13.93 2.66-28.99-2.76-42.22-2.2-5.36-6.53-11.7-8.42-16.89zm-124.66 103.49c-0.05-0.1 4.67 12.2 3.51 10.69 0.38 0 7.46-4.19 11.72-1.93 0.09 0.04 7.37 0.94 7.67 1.4 9.6-0.67 19.32-1.03 28.91 0.48 4.99 0.8 12.26-1.42 16.09 1.4 2.04 1.69 3.07-2.07 4.39 1.27l0.47 0.38c-0.52 0.44 3.02-6.61 5.99-9.55-2.29 9.23-11.96 30.2-16.64 39.3-0.36-0.98-1.15-2.68-1.18-3.6q0.43-0.51 0.85-1.02c-2.02-1.71-14.63-1.9-17.03-2.37-12.22-2.39-23.74 2.59-35.49-2.76 0.62-0.47 1.28-0.77 1.42-1.51l-0.19-0.2c-0.69-0.17-3.92 0.69-4.59 0.81l0.01-0.02c-0.48-3.07-1.08-3.66-2.91-5.91-1.64-3.12-2.87-22.76-3-26.86z"/>
<path class="s3" d="m229.46 24.82c22.38-4.99 46.23-3.52 63.26 13.9-2.94 1.76-2.44 1.37-5.52-0.39-1.43-0.82-3.91-3.96-5-4.09-1.5 1.08-0.84 2.69-3.47 2.7-1.6 0.16-4.72-1.05-6 0.5-2.05 2.55-4.53 0.56-7.19 2.17-3.3 1.99 1.76 2.77 1.25 4.74-0.29 0.31-9.15 1.4-10.69 4.55-2.58 5.28-14.9-3.85-2.01-6 3.28-0.54 4.16-1.21 2.39-4.21-1.21 0.13-5.75 0.69-6.93 1.04-2.64 0.78-13.46 2.1-15.32 0.13-5.43-5.79-7.22-9.32 2.21-9.37l-0.02-0.19c-2.05-2.36-2.01 1.66-6.96-5.48z"/>
<path class="s4" d="m52.76 380.21c1.37 2.56 1.35 8.38 1.48 11.3l-1.27 1.58c-4.76 5.75 0.61 8.2-4.45 13.72-0.83 0.9-0.88 2.54-0.98 3.59-1.84 2.76-0.81 6.37-1.73 8.77-1.8 4.72 1.73 8.02-0.56 11.78-2.34 3.4 1.25 7.72-0.94 10.32q-0.94 0.84-1.89 1.67-0.19 0.17-0.38 0.33c-0.53-17.02 1.6-48.35 10.72-63.06z"/>
<path class="s4" d="m146.83 300c0.83-3.28 2.54-4.24 7.14-4.06 4.96 0.2 3.67-1.38 6.81-1.97 0.4 4.07 5.05 0.81 7.46 2.52 0.78-0.54-11.99 4.75-12.8 5.04-6.77 2.38-12.92 3.93-19.38 7.33-5.8 2.87-2.93 1.63-5.27-0.28-1.44 0.13-1.59 4.98-3.34 3.63-0.26-0.2-2.01-2.29-2.37-2.65 3.82-15.23-2.85 11.41 0 2.23-1.07 3.47-13.54 10.35-17.46 12.26-2.93-2.2 3.96-3.51 4.37-5.03-0.22-0.65-0.83-0.83-0.67-1.57 1.13-1.24 2.95-0.34 3.64-1.95 0.3-0.71-0.28-1.34 0.35-2.22 1.65-0.74 1.43 0.22 3.59-1.15 5.05-4.04 4.65-6.42 9.73-9.97 1.09-0.75 8.74-0.94 8.79-0.92 0.13 0.53 0 1.21 0.24 1.66 2.96 1.13 1.02-5.73 4.18-4.37 0.47 1.14-0.53 2.33-0.17 3.44 3.23-1.43 1.84-1.39 5.16-1.97z"/>
<path class="s5" d="m15.76 614.62c1.83 2.25 2.43 2.84 2.91 5.91l-0.01 0.02c0.67-0.12 3.9-0.98 4.59-0.81l0.19 0.2c-0.14 0.74-0.8 1.04-1.42 1.51 11.75 5.35 23.27 0.37 35.49 2.76 2.4 0.47 15.01 0.66 17.03 2.37q-0.42 0.51-0.85 1.02c0.03 0.92 0.82 2.62 1.18 3.6-10.21 21.3-28.57 52.88-20.97 76.18 1.92 5.9 9.04 18.82 10.42 23.47 4.35 14.58 21.56 61.89 20.91 74.23-2.68 3.12-1.08 2.16-4.82 3.24-11.96-3.71-16.34-30.99-25.42-36.29l-2.27 0.3c-3.56 5.93 1.59 32.99 4.03 40.14 9.22 7.46 24.44 10.78 26.67 20.94-3.82 12.96-39.66-3.08-44.06-10.36-7.77-13.39-16.13-28.79-21.59-43.23-2.98-7.89-1.06-24.82 0.46-33.55 6.82-39.15 5.42-69.96 0.59-109.18-1.25-6.86-2.58-15.54-3.06-22.47z"/>
<path class="s6" d="m426.14 638.12c-0.82-1.16-1.29-1.71-0.49-1.65l4.06-0.66c10.21-1.59 17.16 0.24 27.3 0.03 6.49-0.14 15.56-2.18 22.19-3.09l-0.56 3.13c-2.79 19.29-5.75 37.04-5.41 56.74 0.44 26.21 8.13 54.31 7.54 80.08-0.23 10.16-20.08 49.4-28.96 55.68-5.53 3.92-45.28 21.83-33.91-1.79 1.3-2.67 17.29-9.46 22-12.64 4.1-8.86 7.54-30.98 4.79-40.46l-1.47-1.07c-9.59 4.12-14.95 32.94-26.61 36.34-16.08-7.05 29.95-99.54 28.23-116.21-2.14-20.7-10.15-35.96-18.7-54.43z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

@@ -1,131 +1,35 @@
import SwiftUI
struct BodySilhouetteShape: Shape {
func path(in rect: CGRect) -> Path {
let w = rect.width
let h = rect.height
let mx = w * 0.5
var p = Path()
let headCY = h * 0.09
let headR = w * 0.12
p.addEllipse(in: CGRect(
x: mx - headR, y: headCY - headR,
width: headR * 2, height: headR * 2
))
let neckW = w * 0.06
let neckTop = headCY + headR
let shoulderY = h * 0.20
p.addRect(CGRect(
x: mx - neckW, y: neckTop,
width: neckW * 2, height: shoulderY - neckTop
))
let shoulderHW = w * 0.42
let armOuterBotY = h * 0.44
let armInnerBotY = h * 0.44
let armOuterX = w * 0.06
let armInnerX = w * 0.12
p.move(to: CGPoint(x: mx - neckW, y: shoulderY))
p.addLine(to: CGPoint(x: mx - shoulderHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx - armOuterX, y: armOuterBotY))
p.addLine(to: CGPoint(x: mx - armInnerX, y: armInnerBotY))
p.closeSubpath()
p.move(to: CGPoint(x: mx + neckW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + shoulderHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + armOuterX, y: armOuterBotY))
p.addLine(to: CGPoint(x: mx + armInnerX, y: armInnerBotY))
p.closeSubpath()
let torsoHW = w * 0.18
let waistY = h * 0.46
let waistHW = w * 0.15
p.move(to: CGPoint(x: mx - torsoHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + torsoHW, y: shoulderY))
p.addLine(to: CGPoint(x: mx + waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx - waistHW, y: waistY))
p.closeSubpath()
let hipHW = w * 0.22
let hipY = h * 0.52
p.move(to: CGPoint(x: mx - waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx + waistHW, y: waistY))
p.addLine(to: CGPoint(x: mx + hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx - hipHW, y: hipY))
p.closeSubpath()
let gap = w * 0.02
let legBotY = h * 0.92
let footH = h * 0.05
let footExtra = w * 0.04
p.move(to: CGPoint(x: mx - hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx - gap, y: hipY))
p.addLine(to: CGPoint(x: mx - gap, y: legBotY))
p.addLine(to: CGPoint(x: mx - hipHW - footExtra, y: legBotY + footH))
p.addLine(to: CGPoint(x: mx - hipHW, y: legBotY))
p.closeSubpath()
p.move(to: CGPoint(x: mx + gap, y: hipY))
p.addLine(to: CGPoint(x: mx + hipHW, y: hipY))
p.addLine(to: CGPoint(x: mx + hipHW, y: legBotY))
p.addLine(to: CGPoint(x: mx + hipHW + footExtra, y: legBotY + footH))
p.addLine(to: CGPoint(x: mx + gap, y: legBotY))
p.closeSubpath()
return p
}
}
/// Body-zone highlight icon backed by vector SVG assets in `Assets.xcassets`.
///
/// Each zone renders its own artwork:
/// - `upper-body` `ZoneIconUpper`
/// - `lower-body` `ZoneIconLower`
/// - `full-body` `ZoneIconFull`
///
/// The SVGs ship with their own baked-in colors, so the icon is **not**
/// tinted by `Theme.zoneGradient` it shows the artwork as authored.
/// To make icons auto-recolor per zone instead, switch the imagesets to
/// single-color SVGs and set `"template-rendering-intent": "template"`
/// in each `Contents.json`, then apply `.foregroundStyle(...)` here.
///
/// Drawn on a 56×80 frame with `scaledToFit()` so the SVG keeps its
/// native aspect ratio inside the card.
struct ZoneHighlightIcon: View {
let zone: String
private var waistFraction: CGFloat { 0.50 }
var body: some View {
let shape = BodySilhouetteShape()
ZStack {
shape
.fill(.white.opacity(0.12))
shape
.fill(zoneGradient)
.mask(zoneMask)
}
.frame(width: 56, height: 80)
Image(decorative: imageName, bundle: .main)
.resizable()
.scaledToFit()
.frame(width: 56, height: 80)
}
private var zoneGradient: LinearGradient {
switch zone {
case "upper-body":
LinearGradient(colors: [.orange, .red.opacity(0.8)], startPoint: .top, endPoint: .bottom)
case "lower-body":
LinearGradient(colors: [.blue, .purple.opacity(0.8)], startPoint: .top, endPoint: .bottom)
case "full-body":
LinearGradient(colors: [Theme.brand, .purple], startPoint: .top, endPoint: .bottom)
default:
LinearGradient(colors: [.gray, .secondary], startPoint: .top, endPoint: .bottom)
}
}
@ViewBuilder
private var zoneMask: some View {
switch zone {
case "upper-body":
Rectangle()
.frame(height: 80 * waistFraction)
.frame(maxHeight: .infinity, alignment: .top)
case "lower-body":
Rectangle()
.frame(height: 80 * waistFraction)
.frame(maxHeight: .infinity, alignment: .bottom)
case "full-body":
Rectangle()
default:
Rectangle()
private var imageName: String {
switch zone.lowercased() {
case "upper-body", "upper": return "ZoneIconUpper"
case "lower-body", "lower": return "ZoneIconLower"
default: return "ZoneIconFull"
}
}
}

View File

@@ -216,6 +216,8 @@ schemes:
targets:
TabataGo: all
TabataGoWidget: all
TabataGoWatch: all
TabataGoWatchWidget: all
run:
config: Debug
archive:

View File

@@ -1,3 +1,3 @@
{
"version": "1.0.0"
"version": "1.0.1"
}