Compare commits
1 Commits
fix/live-a
...
c938c81df0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c938c81df0 |
41
.github/workflows/admin-web-tests.yml
vendored
Normal file
41
.github/workflows/admin-web-tests.yml
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
name: Admin Web Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
node-version:
|
||||
type: string
|
||||
default: '22'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
defaults:
|
||||
run:
|
||||
working-directory: admin-web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
cache: 'npm'
|
||||
cache-dependency-path: admin-web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Type check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Run unit tests
|
||||
run: npx vitest run
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npx playwright test
|
||||
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
@@ -38,34 +38,9 @@ jobs:
|
||||
name: Admin Web CI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.admin-web == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: admin-web
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: admin-web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Type check
|
||||
run: npx tsc --noEmit
|
||||
|
||||
- name: Run unit tests
|
||||
run: npx vitest run
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run E2E tests
|
||||
run: npx playwright test
|
||||
uses: ./.github/workflows/admin-web-tests.yml
|
||||
with:
|
||||
node-version: '22'
|
||||
|
||||
# ── YouTube Worker: Node.js microservice ──
|
||||
youtube-worker-check:
|
||||
|
||||
154
.github/workflows/docker-admin-web.yml
vendored
Normal file
154
.github/workflows/docker-admin-web.yml
vendored
Normal file
@@ -0,0 +1,154 @@
|
||||
name: Docker Admin Web
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- 'admin-web/**'
|
||||
- '.github/workflows/docker-admin-web.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: docker-admin-web-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
admin-web-test:
|
||||
uses: ./.github/workflows/admin-web-tests.yml
|
||||
|
||||
semantic-release:
|
||||
name: Semantic Release
|
||||
needs: admin-web-test
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
defaults:
|
||||
run:
|
||||
working-directory: admin-web
|
||||
outputs:
|
||||
released: ${{ steps.check-release.outputs.released }}
|
||||
version: ${{ steps.check-release.outputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
cache-dependency-path: admin-web/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Record version before release
|
||||
id: before
|
||||
run: echo "version=$(node -p "require('./version.json').version")" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Run semantic-release
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.CI_GITEA_TOKEN }}
|
||||
GITEA_URL: https://gitea.1000co.fr
|
||||
run: npx semantic-release
|
||||
|
||||
- name: Check if released
|
||||
id: check-release
|
||||
run: |
|
||||
VERSION=$(node -p "require('./version.json').version")
|
||||
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
||||
if [ "${{ steps.before.outputs.version }}" != "$VERSION" ]; then
|
||||
echo "released=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "released=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
build-and-push:
|
||||
name: Build & Push Docker Image
|
||||
needs: semantic-release
|
||||
if: needs.semantic-release.outputs.released == 'true' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Install Docker CLI
|
||||
run: apt-get update -qq && apt-get install -y -qq docker.io
|
||||
|
||||
- name: Login to Gitea registry
|
||||
run: |
|
||||
echo "${{ secrets.DOCKER_PASSWORD }}" | \
|
||||
docker login gitea.1000co.fr \
|
||||
-u "${{ secrets.DOCKER_USERNAME }}" \
|
||||
--password-stdin
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
U="${{ secrets.DOCKER_USERNAME }}"
|
||||
V="${{ needs.semantic-release.outputs.version }}"
|
||||
docker build \
|
||||
--file ./admin-web/Dockerfile \
|
||||
--tag "gitea.1000co.fr/$U/tabatago-admin-web:$V" \
|
||||
--tag "gitea.1000co.fr/$U/tabatago-admin-web:latest" \
|
||||
./admin-web
|
||||
docker push "gitea.1000co.fr/$U/tabatago-admin-web:$V"
|
||||
docker push "gitea.1000co.fr/$U/tabatago-admin-web:latest"
|
||||
|
||||
deploy:
|
||||
name: Deploy to Portainer
|
||||
needs: [semantic-release, build-and-push]
|
||||
if: needs.semantic-release.outputs.released == 'true' || github.event_name == 'workflow_dispatch'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install deps
|
||||
run: apt-get update -qq && apt-get install -y -qq gettext-base jq curl
|
||||
|
||||
- name: Deploy stack to Portainer
|
||||
env:
|
||||
PORTAINER_TOKEN: ${{ secrets.PORTAINER_ACCESS_TOKEN }}
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
VERSION: ${{ needs.semantic-release.outputs.version }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
# Substitute env vars in compose file
|
||||
export DOCKER_USERNAME VERSION
|
||||
COMPOSE=$(envsubst '$DOCKER_USERNAME $VERSION' < admin-web/docker-compose.portainer.yml)
|
||||
|
||||
# Authenticate and discover Docker endpoint (first endpoint = local Docker)
|
||||
ENDPOINT_ID=$(curl -sSf -H "X-API-Key: $PORTAINER_TOKEN" \
|
||||
"https://portainer.1000co.fr/api/endpoints" | jq -r '.[0].Id')
|
||||
echo "Endpoint ID: $ENDPOINT_ID"
|
||||
|
||||
# Check if "tabatago-admin-web" stack already exists
|
||||
STACK_ID=$(curl -sSf -H "X-API-Key: $PORTAINER_TOKEN" \
|
||||
"https://portainer.1000co.fr/api/stacks" | jq -r '.[] | select(.Name == "tabatago-admin-web") | .Id')
|
||||
|
||||
if [ -n "$STACK_ID" ] && [ "$STACK_ID" != "null" ]; then
|
||||
echo "Stack tabatago-admin-web exists (ID: $STACK_ID), updating compose content..."
|
||||
HTTP_RESPONSE=$(curl -s -w '\n%{http_code}' \
|
||||
-X PUT \
|
||||
-H "X-API-Key: $PORTAINER_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$(jq -n --arg content "$COMPOSE" '{stackFileContent: $content, prune: false, pullImage: true}')" \
|
||||
"https://portainer.1000co.fr/api/stacks/$STACK_ID?endpointId=$ENDPOINT_ID" || true)
|
||||
HTTP_BODY=$(echo "$HTTP_RESPONSE" | sed '$d')
|
||||
HTTP_CODE=$(echo "$HTTP_RESPONSE" | tail -1)
|
||||
|
||||
if [ -z "$HTTP_CODE" ] || [ "$HTTP_CODE" -ge 400 ]; then
|
||||
echo "Portainer API error (HTTP ${HTTP_CODE:-connection failed}):"
|
||||
echo "$HTTP_BODY"
|
||||
exit 1
|
||||
fi
|
||||
echo "Stack updated. Portainer will redeploy with new images."
|
||||
else
|
||||
echo "⚠️ Stack tabatago-admin-web not found in Portainer."
|
||||
echo "Create it manually in Portainer first: Stacks → Add stack → paste compose content from admin-web/docker-compose.portainer.yml"
|
||||
exit 1
|
||||
fi
|
||||
8
.github/workflows/pr-iphone-deploy.yml
vendored
8
.github/workflows/pr-iphone-deploy.yml
vendored
@@ -106,15 +106,16 @@ jobs:
|
||||
|
||||
# 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
|
||||
xcrun devicectl device install app --device "$UDID" "$APP" 2>&1 | tail -3
|
||||
if [ ${PIPESTATUS[0]} -eq 0 ]; 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
|
||||
ios-deploy --bundle "$APP" --id "$UDID" --justlaunch
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "✅ Installed via USB"
|
||||
exit 0
|
||||
fi
|
||||
@@ -122,7 +123,6 @@ jobs:
|
||||
echo "❌ Install failed (both WiFi and USB)"
|
||||
exit 1
|
||||
|
||||
|
||||
- name: Post "Ready to test" comment
|
||||
env:
|
||||
GT_TOKEN: ${{ secrets.PR_API_TOKEN }}
|
||||
|
||||
12
admin-web/.dockerignore
Normal file
12
admin-web/.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
.next
|
||||
.git
|
||||
.gitignore
|
||||
README.md
|
||||
.npm
|
||||
.pnp
|
||||
.DS_Store
|
||||
*.md
|
||||
coverage
|
||||
playwright-report
|
||||
test-results
|
||||
14
admin-web/.releaserc.json
Normal file
14
admin-web/.releaserc.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"branches": ["main"],
|
||||
"plugins": [
|
||||
"@semantic-release/commit-analyzer",
|
||||
"@semantic-release/release-notes-generator",
|
||||
["@semantic-release/exec", {
|
||||
"prepareCmd": "echo '{\"version\": \"${nextRelease.version}\"}' > version.json"
|
||||
}],
|
||||
["@semantic-release/git", {
|
||||
"assets": ["version.json"],
|
||||
"message": "chore(release): ${nextRelease.version}\n\n${nextRelease.notes}"
|
||||
}]
|
||||
]
|
||||
}
|
||||
29
admin-web/Dockerfile
Normal file
29
admin-web/Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM node:22-bookworm-slim AS build
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN npm run build
|
||||
|
||||
FROM node:22-bookworm-slim AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=build /app/public ./public
|
||||
COPY --from=build /app/.next/standalone ./
|
||||
COPY --from=build /app/.next/static ./.next/static
|
||||
|
||||
RUN groupadd -g 1001 appgroup && useradd -u 1001 -g appgroup -s /bin/sh -M nodeuser
|
||||
|
||||
USER nodeuser
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["node", "server.js"]
|
||||
16
admin-web/docker-compose.portainer.yml
Normal file
16
admin-web/docker-compose.portainer.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
admin-web:
|
||||
image: gitea.1000co.fr/${DOCKER_USERNAME}/tabatago-admin-web:${VERSION:-latest}
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}
|
||||
- NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}
|
||||
networks:
|
||||
- supabase_supabase-network
|
||||
|
||||
networks:
|
||||
supabase_supabase-network:
|
||||
external: true
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: 'standalone',
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -13,7 +13,8 @@
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"test:all": "npm run test && npm run test:e2e"
|
||||
"test:all": "npm run test && npm run test:e2e",
|
||||
"semantic-release": "semantic-release"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/genai": "^1.47.0",
|
||||
@@ -30,6 +31,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.58.2",
|
||||
"@semantic-release/commit-analyzer": "^13.0.0",
|
||||
"@semantic-release/exec": "^6.0.0",
|
||||
"@semantic-release/git": "^10.0.0",
|
||||
"@semantic-release/release-notes-generator": "^14.0.0",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
@@ -42,6 +47,7 @@
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"jsdom": "^28.1.0",
|
||||
"semantic-release": "^24.0.0",
|
||||
"shadcn": "^3.8.5",
|
||||
"tailwindcss": "^4",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
|
||||
1
admin-web/version.json
Normal file
1
admin-web/version.json
Normal file
@@ -0,0 +1 @@
|
||||
{"version": "0.1.0"}
|
||||
@@ -18,8 +18,17 @@
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>tabatago</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>TabataGo reads your health data to show fitness stats and personalize your workouts.</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
<string>health-records</string>
|
||||
</array>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>group.fr.millianlmx.tabatago</string>
|
||||
</array>
|
||||
<array/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -28,55 +28,12 @@ final class AudioService {
|
||||
try audioSession.setCategory(
|
||||
.playback,
|
||||
mode: .default,
|
||||
options: [.allowAirPlay, .allowBluetooth]
|
||||
options: [.mixWithOthers, .allowAirPlay, .allowBluetooth]
|
||||
)
|
||||
try audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Session configuration failed: \(error)")
|
||||
}
|
||||
|
||||
// ─── Interruption Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.interruptionNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let typeValue = notification.userInfo?[AVAudioSessionInterruptionTypeKey] as? UInt,
|
||||
let type = AVAudioSession.InterruptionType(rawValue: typeValue) else { return }
|
||||
|
||||
if type == .ended {
|
||||
guard let optionsValue = notification.userInfo?[AVAudioSessionInterruptionOptionKey] as? UInt,
|
||||
AVAudioSession.InterruptionOptions(rawValue: optionsValue).contains(.shouldResume) else { return }
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Re-activation failed after interruption: \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Route Change Handler
|
||||
NotificationCenter.default.addObserver(
|
||||
forName: AVAudioSession.routeChangeNotification,
|
||||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self,
|
||||
let reasonValue = notification.userInfo?[AVAudioSessionRouteChangeReasonKey] as? UInt,
|
||||
let reason = AVAudioSession.RouteChangeReason(rawValue: reasonValue) else { return }
|
||||
|
||||
switch reason {
|
||||
case .newDeviceAvailable, .oldDeviceUnavailable:
|
||||
do {
|
||||
try self.audioSession.setActive(true)
|
||||
} catch {
|
||||
print("[Audio] Route re-activation failed: \(error)")
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Phase Audio Cues ─────────────────────────────────────────
|
||||
|
||||
@@ -39,7 +39,6 @@ final class PlayerViewModel: ObservableObject {
|
||||
@Published var isComplete: Bool = false
|
||||
@Published var showExitConfirmation: Bool = false
|
||||
@Published private(set) var completedSession: WorkoutSession? = nil
|
||||
@Published var liveActivityError: String? = nil
|
||||
|
||||
// Track info for Live Activity — set by View when music changes
|
||||
var currentTrackTitle = ""
|
||||
@@ -383,10 +382,7 @@ final class PlayerViewModel: ObservableObject {
|
||||
// ─── Dynamic Island / Live Activity ─────────────────────────────
|
||||
|
||||
func syncActivity(shouldAlert: Bool = false) {
|
||||
guard ActivityAuthorizationInfo().areActivitiesEnabled else {
|
||||
liveActivityError = "Live Activities are disabled in Settings"
|
||||
return
|
||||
}
|
||||
guard ActivityAuthorizationInfo().areActivitiesEnabled else { return }
|
||||
guard isRunning else { return }
|
||||
|
||||
let isPlayingMusic = (phase == .work || phase == .rest) && isRunning && !isPaused
|
||||
@@ -453,14 +449,8 @@ final class PlayerViewModel: ObservableObject {
|
||||
attributes: attrs,
|
||||
content: ActivityContent(state: state, staleDate: staleDate)
|
||||
)
|
||||
liveActivityError = nil
|
||||
observeActivityState()
|
||||
} catch let authError as ActivityAuthorizationError {
|
||||
liveActivityError = "Live Activities: \(authError.localizedDescription)"
|
||||
} catch {
|
||||
let msg = error.localizedDescription
|
||||
let truncated = msg.count > 200 ? String(msg.prefix(200)) + "…" : msg
|
||||
liveActivityError = "Live Activity error: \(truncated)"
|
||||
print("❌ Failed to start Live Activity: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,40 +66,6 @@ struct PlayerView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// Live Activity error banner (non-blocking)
|
||||
// ══════════════════════════════════════════════════
|
||||
if let error = vm.liveActivityError {
|
||||
VStack {
|
||||
HStack(spacing: 8) {
|
||||
Image(systemName: "exclamationmark.triangle.fill")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.yellow)
|
||||
Text(error)
|
||||
.font(.caption2.weight(.medium))
|
||||
.foregroundStyle(.white.opacity(0.9))
|
||||
Spacer()
|
||||
Button {
|
||||
vm.liveActivityError = nil
|
||||
} label: {
|
||||
Image(systemName: "xmark")
|
||||
.font(.system(size: 10, weight: .bold))
|
||||
.foregroundStyle(.white.opacity(0.6))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 10)
|
||||
.background(.ultraThinMaterial.opacity(0.9))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 12))
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.top, 60)
|
||||
Spacer()
|
||||
}
|
||||
.transition(.move(edge: .top).combined(with: .opacity))
|
||||
.animation(.spring(duration: 0.3), value: vm.liveActivityError)
|
||||
.zIndex(100)
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════
|
||||
// Layer 4 — Compact timer ring (top-right corner)
|
||||
// ══════════════════════════════════════════════════
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>2</string>
|
||||
<string>1</string>
|
||||
<key>NSHealthShareUsageDescription</key>
|
||||
<string>TabataGo reads your heart rate and calories during workouts.</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>TabataGoWidget</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -553,7 +553,7 @@ struct WorkoutLiveActivity: Widget {
|
||||
.contentMargins(.leading, 8, for: .expanded)
|
||||
.contentMargins(.trailing, 8, for: .expanded)
|
||||
.contentMargins(.bottom, 6, for: .expanded)
|
||||
.widgetURL(URL(string: "tabatago://workout"))
|
||||
.widgetURL(URL(string: "tabatago://workout")!)
|
||||
}
|
||||
.supplementalActivityFamilies([.small, .medium])
|
||||
}
|
||||
|
||||
@@ -60,10 +60,6 @@ targets:
|
||||
SUPABASE_ANON_KEY: $(SUPABASE_ANON_KEY)
|
||||
REVENUECAT_API_KEY: $(REVENUECAT_API_KEY)
|
||||
POSTHOG_API_KEY: $(POSTHOG_API_KEY)
|
||||
NSSupportsLiveActivities: true
|
||||
NSSupportsLiveActivitiesFrequentUpdates: true
|
||||
UIBackgroundModes:
|
||||
- audio
|
||||
entitlements:
|
||||
path: TabataGo/Resources/TabataGo.entitlements
|
||||
properties:
|
||||
@@ -80,8 +76,6 @@ targets:
|
||||
|
||||
- target: TabataGoWatch
|
||||
embed: true
|
||||
- target: TabataGoWidget
|
||||
embed: true
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago
|
||||
@@ -162,30 +156,6 @@ targets:
|
||||
TARGETED_DEVICE_FAMILY: "4"
|
||||
WATCHOS_DEPLOYMENT_TARGET: "11.0"
|
||||
|
||||
TabataGoWidget:
|
||||
type: app-extension
|
||||
platform: iOS
|
||||
deploymentTarget: "26.0"
|
||||
sources:
|
||||
- path: TabataGoWidget
|
||||
excludes:
|
||||
- "**/.DS_Store"
|
||||
- path: TabataGo/Models/WorkoutActivityAttributes.swift
|
||||
- path: TabataGo/Models/MusicActivityAttributes.swift
|
||||
info:
|
||||
path: TabataGoWidget/Info.plist
|
||||
properties:
|
||||
NSExtension:
|
||||
NSExtensionPointIdentifier: com.apple.widgetkit-extension
|
||||
NSSupportsLiveActivities: true
|
||||
NSSupportsLiveActivitiesFrequentUpdates: true
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: fr.millianlmx.tabatago.widget
|
||||
IPHONEOS_DEPLOYMENT_TARGET: "26.0"
|
||||
TARGETED_DEVICE_FAMILY: "1"
|
||||
ENABLE_PREVIEWS: YES
|
||||
|
||||
TabataGoTests:
|
||||
type: bundle.unit-test
|
||||
platform: iOS
|
||||
@@ -215,7 +185,6 @@ schemes:
|
||||
build:
|
||||
targets:
|
||||
TabataGo: all
|
||||
TabataGoWidget: all
|
||||
run:
|
||||
config: Debug
|
||||
archive:
|
||||
|
||||
Reference in New Issue
Block a user