feat(app): App-Fundament – Flutter-Gerüst und Supabase-Konfiguration #32

Merged
cschlaefke merged 11 commits from feature/31-app-fundament into main 2026-11-02 21:21:02 +00:00
124 changed files with 5419 additions and 23 deletions

View file

@ -0,0 +1,19 @@
#!/usr/bin/env bash
# pgTAP-Tests gegen den laufenden Supabase-Stack ausführen, wenn der Docker-Daemon remote ist
# (DOCKER_HOST=tcp://..., z. B. dind). `supabase test db` bindet supabase/tests per Host-Pfad ein;
# dieser Pfad existiert im dind nicht (Ergebnis: "no pgTAP tests found"). Daher: Tests per
# `docker cp` in einen pg_prove-Container kopieren. Voraussetzung: `supabase start` lief.
set -euo pipefail
PROJECT_ID="${SUPABASE_PROJECT_ID:-shuffle}" # = project_id in supabase/config.toml
PG_PROVE_IMAGE="${PG_PROVE_IMAGE:-public.ecr.aws/supabase/pg_prove:3.36}" # wie in Supabase CLI 3.0.0
# pgTAP wie `supabase test db` vorab aktivieren (die Tests rufen nur plan() etc. auf)
docker exec "supabase_db_${PROJECT_ID}" psql -U postgres -d postgres -v ON_ERROR_STOP=1 \
-c "create extension if not exists pgtap with schema extensions"
cid=$(docker create --network "supabase_network_${PROJECT_ID}" -e PGPASSWORD=postgres \
"$PG_PROVE_IMAGE" pg_prove --ext .sql -r -h "supabase_db_${PROJECT_ID}" -U postgres -d postgres /tests)
trap 'docker rm -f "$cid" >/dev/null 2>&1 || true' EXIT
docker cp supabase/tests "$cid:/"
docker start -a "$cid"

View file

@ -1,4 +1,5 @@
# CI für Pull Requests und main – läuft auf einem Forgejo-Runner mit Docker-Label # CI für Pull Requests und main – läuft auf einem Forgejo-Runner mit Docker-Label.
# Flutter-Projekt liegt in app/, Supabase in supabase/.
name: CI name: CI
on: on:
@ -6,37 +7,124 @@ on:
push: push:
branches: [main] branches: [main]
env:
SUPABASE_CLI_VERSION: 3.0.0
# Einzige Stelle für die Flutter-Version (= lokale Version, Dart 3.13.7; muss zu app/pubspec.yaml passen).
# Muss ein existierendes Tag in github.com/flutter/flutter sein. Ein Cirrus-Image mit dieser Version gibt es nicht.
FLUTTER_VERSION: 3.47.9
FLUTTER_HOME: /opt/flutter
jobs: jobs:
# Format, Analyse, Tests. Generierte Dateien (*.g.dart, gen-l10n) sind eingecheckt und
# werden hier neu erzeugt; ein Diff bedeutet: Generierung im PR vergessen.
flutter: flutter:
runs-on: docker runs-on: docker
container: container:
image: ghcr.io/cirruslabs/flutter:stable # für Releases auf feste Version pinnen image: node:22.12-bookworm # gepinnt; enthält git, curl, unzip, xz und Node (für JS-Actions)
env:
PUB_CACHE: ${{ github.workspace }}/.pub-cache
defaults: defaults:
run: run:
working-directory: app working-directory: app
steps: steps:
- uses: https://code.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v4
- name: Flutter-SDK-Cache
id: flutter-cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-node22.12-bookworm
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter --version
flutter precache --web
working-directory: .
- name: Pub-Cache - name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4 uses: https://code.forgejo.org/actions/cache@v4
with: with:
path: /root/.pub-cache path: ${{ github.workspace }}/.pub-cache
key: pub-${{ hashFiles('app/pubspec.lock') }} key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Abhängigkeiten - name: Abhängigkeiten (erzeugt auch gen-l10n)
run: flutter pub get run: flutter pub get
- name: Code-Generierung - name: Code-Generierung
run: dart run build_runner build --delete-conflicting-outputs run: dart run build_runner build --delete-conflicting-outputs
- name: Generierte Dateien aktuell?
run: |
git config --global --add safe.directory "$GITHUB_WORKSPACE"
if ! git diff --exit-code -- . ; then
echo "::error::Generierte Dateien sind nicht aktuell. Lokal 'dart run build_runner build --delete-conflicting-outputs' und 'flutter gen-l10n' ausführen und committen."
exit 1
fi
untracked=$(git ls-files --others --exclude-standard -- lib test)
if [ -n "$untracked" ]; then echo "Nicht eingecheckte generierte Dateien:"; echo "$untracked"; exit 1; fi
- name: Format - name: Format
run: dart format --set-exit-if-changed lib test run: dart format --set-exit-if-changed .
- name: Analyse - name: Analyse
run: flutter analyze --fatal-infos run: flutter analyze --fatal-infos
- name: Tests - name: Tests
run: flutter test --coverage run: flutter test
# Web-Build parallel zu den Prüfungen; Platzhalterwerte aus app/env/ci.example.json (keine Secrets).
web-build:
runs-on: docker
container:
image: node:22.12-bookworm # gepinnt; enthält git, curl, unzip, xz und Node (für JS-Actions)
env:
PUB_CACHE: ${{ github.workspace }}/.pub-cache
defaults:
run:
working-directory: app
steps:
- uses: https://code.forgejo.org/actions/checkout@v4
- name: Flutter-SDK-Cache
id: flutter-cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ env.FLUTTER_HOME }}
key: flutter-${{ env.FLUTTER_VERSION }}-node22.12-bookworm
- name: Flutter installieren
run: |
if [ ! -x "$FLUTTER_HOME/bin/flutter" ]; then
rm -rf "$FLUTTER_HOME"
git clone --depth 1 --branch "$FLUTTER_VERSION" https://github.com/flutter/flutter.git "$FLUTTER_HOME"
fi
echo "$FLUTTER_HOME/bin" >> "$GITHUB_PATH"
export PATH="$FLUTTER_HOME/bin:$PATH"
export CI=true
flutter config --no-analytics --enable-web
flutter --version
flutter precache --web
working-directory: .
- name: Pub-Cache
uses: https://code.forgejo.org/actions/cache@v4
with:
path: ${{ github.workspace }}/.pub-cache
key: pub-${{ env.FLUTTER_VERSION }}-${{ hashFiles('app/pubspec.lock') }}
restore-keys: pub-${{ env.FLUTTER_VERSION }}-
- name: Abhängigkeiten
run: flutter pub get
- name: Web-Build - name: Web-Build
run: flutter build web --release --dart-define-from-file=env/ci.example.json run: flutter build web --release --dart-define-from-file=env/ci.example.json
@ -44,9 +132,11 @@ jobs:
supabase: supabase:
runs-on: docker runs-on: docker
container: container:
image: node:22-bookworm image: node:22.12-bookworm
steps: steps:
- uses: https://code.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v4
with:
fetch-depth: 0
- name: Migrationen prüfen (ohne laufende DB) - name: Migrationen prüfen (ohne laufende DB)
run: | run: |
test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; } test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; }
@ -57,20 +147,38 @@ jobs:
if [ -n "$changed" ]; then echo "Geänderte bestehende Migrationen: $changed"; exit 1; fi if [ -n "$changed" ]; then echo "Geänderte bestehende Migrationen: $changed"; exit 1; fi
fi fi
# RLS-/pgTAP-Tests: braucht einen Runner mit Docker-Socket (Label "docker-host"). # RLS-/pgTAP-Tests: braucht einen Runner mit Docker-Zugriff (Label "docker-host").
# Läuft nur, wenn Migrationen existieren. Ohne passenden Runner Job bei Bedarf deaktivieren. # Der Job-Container hat kein Docker-CLI und keinen Socket (Isolation, Repo ist öffentlich). Stattdessen
# wird ein gepinntes Docker-CLI installiert und der dind-Daemon des Runners per TCP angesprochen
# (erreichbar über das Default-Gateway des Job-Netzes). Supabase-Ports liegen dann im dind;
# die Supabase-CLI nimmt den Host aus DOCKER_HOST automatisch.
# Nutzt die eingecheckte supabase/config.toml. Ohne passenden Runner Job bei Bedarf deaktivieren.
supabase-tests: supabase-tests:
runs-on: docker-host runs-on: docker-host
env:
DOCKER_CLI_VERSION: 27.3.1
DIND_PORT: 2375
steps: steps:
- uses: https://code.forgejo.org/actions/checkout@v4 - uses: https://code.forgejo.org/actions/checkout@v4
- uses: https://code.forgejo.org/actions/setup-node@v4 - name: Docker-CLI installieren und dind anbinden
with:
node-version: 22
- name: Supabase starten und Datenbanktests ausführen
run: | run: |
arch=$(uname -m)
curl -fsSL "https://download.docker.com/linux/static/stable/${arch}/docker-${DOCKER_CLI_VERSION}.tgz" \
| tar -xz -C /usr/local/bin --strip-components=1 docker/docker
# Default-Gateway des Job-Containers (= dind-Host) aus /proc/net/route; `ip` fehlt im Image
hex=$(awk '$2=="00000000"{print $3; exit}' /proc/net/route)
gw=$(printf '%d.%d.%d.%d' "0x${hex:6:2}" "0x${hex:4:2}" "0x${hex:2:2}" "0x${hex:0:2}")
echo "DOCKER_HOST=tcp://${gw}:${DIND_PORT}" >> "$GITHUB_ENV"
DOCKER_HOST="tcp://${gw}:${DIND_PORT}" docker version --format 'dind {{.Server.Version}}'
- name: Supabase starten
run: |
test -f supabase/config.toml || { echo "supabase/config.toml fehlt"; exit 1; }
test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; } test -d supabase/migrations || { echo "Keine Migrationen – übersprungen"; exit 0; }
npx --yes supabase start -x studio,imgproxy,edge-runtime,logflare,vector npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} start -x studio,imgproxy,edge-runtime,logflare,vector
npx --yes supabase test db - name: Datenbanktests (pgTAP)
run: |
test -d supabase/migrations || exit 0
bash .forgejo/scripts/supabase-db-tests.sh
- name: Aufräumen - name: Aufräumen
if: always() if: always()
run: npx --yes supabase stop --no-backup || true run: npx --yes supabase@${{ env.SUPABASE_CLI_VERSION }} stop --no-backup || true

6
.gitignore vendored
View file

@ -3,11 +3,17 @@
.env.* .env.*
env/*.json env/*.json
!env/*.example.json !env/*.example.json
app/env/*.json
!app/env/*.example.json
supabase/.env supabase/.env
**/key.properties **/key.properties
**/*.jks **/*.jks
**/*.keystore **/*.keystore
# Supabase CLI (lokaler Zustand)
supabase/.temp/
supabase/.branches/
# Claude Code – persönliche Einstellungen # Claude Code – persönliche Einstellungen
.claude/settings.local.json .claude/settings.local.json
CLAUDE.local.md CLAUDE.local.md

View file

@ -12,7 +12,7 @@ User Stories in Forgejo-Issues umgesetzt.
- **Frontend:** Flutter (stable) / Dart 3 – eine Codebasis für iOS, Android, Web - **Frontend:** Flutter (stable) / Dart 3 – eine Codebasis für iOS, Android, Web
- **State-Management:** Riverpod (mit Code-Generierung) - **State-Management:** Riverpod (mit Code-Generierung)
- **Navigation:** go_router (Deep Links + Web-URLs) - **Navigation:** go_router (Deep Links + Web-URLs)
- **Backend:** Supabase (PostgreSQL 16, Auth, Storage, Realtime, Edge Functions), **selbst gehostet auf Hetzner** (ADR-0002); lokal per Supabase CLI. Nur Features nutzen, die auch self-hosted verfügbar sind; Dashboard-Einstellungen im Repo dokumentieren. - **Backend:** Supabase (PostgreSQL 17, Auth, Storage, Realtime, Edge Functions), **selbst gehostet auf Hetzner** (ADR-0002); lokal per Supabase CLI. Nur Features nutzen, die auch self-hosted verfügbar sind; Dashboard-Einstellungen im Repo dokumentieren.
- **Modelle:** freezed + json_serializable - **Modelle:** freezed + json_serializable
- **Tests:** flutter_test, mocktail, integration_test, Playwright (Web-E2E) - **Tests:** flutter_test, mocktail, integration_test, Playwright (Web-E2E)
- **CI/CD:** Forgejo Actions (`.forgejo/workflows/`) - **CI/CD:** Forgejo Actions (`.forgejo/workflows/`)

48
app/.gitignore vendored Normal file
View file

@ -0,0 +1,48 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins-dependencies
.pub-cache/
.pub/
/build/
/coverage/
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
# Widget Preview related
.widget_preview/

36
app/.metadata Normal file
View file

@ -0,0 +1,36 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: "c197ca5199e9ee5661e865596c9c07294dfab51c"
channel: "stable"
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
base_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
- platform: android
create_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
base_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
- platform: ios
create_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
base_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
- platform: web
create_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
base_revision: c197ca5199e9ee5661e865596c9c07294dfab51c
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

42
app/README.md Normal file
View file

@ -0,0 +1,42 @@
# Shuffle – Flutter-App
Brettspiel-App für iOS, Android und Web (eine Codebasis). Architektur und Konventionen: `../CLAUDE.md`, `../docs/adr/`.
## Struktur
```
lib/
core/ config, logging, error, result, supabase, routing, l10n, theme
features/ <feature>/{data,domain,presentation}
l10n/ app_de.arb, app_en.arb (+ generierte AppLocalizations)
test/ Spiegel von lib/
env/ *.example.json (echte Umgebungsdateien sind git-ignoriert)
```
## Starten
```bash
cd app
flutter pub get
cp env/dev.example.json env/dev.json # Werte anpassen
dart run build_runner build --delete-conflicting-outputs
flutter run -d chrome --dart-define-from-file=env/dev.json
```
Pflichtwerte: `SUPABASE_URL`, `SUPABASE_ANON_KEY` (der Start bricht ohne sie ab).
Optional mit Platzhalter: `SITE_URL` (Default `http://localhost:3000`; für Staging/Prod zwingend setzen, sonst zeigen Mail-Links auf localhost), `LEGAL_PRIVACY_URL`, `LEGAL_TERMS_URL`, `TERMS_VERSION`, `APP_ENV`.
## Platzhalter-Identität (vor dem Store-Release festlegen)
| Was | Wo ändern |
|---|---|
| Bundle-/Application-ID `work.cshomelabs.shuffle` | `android/app/build.gradle.kts` (`namespace`, `applicationId`), Kotlin-Package unter `android/app/src/main/kotlin/`, `ios/Runner.xcodeproj/project.pbxproj` (`PRODUCT_BUNDLE_IDENTIFIER`) |
| App-Name „Shuffle“ | `android/app/src/main/AndroidManifest.xml`, `ios/Runner/Info.plist`, `web/index.html`, `web/manifest.json`, l10n-Schlüssel `appTitle` |
| Web-Domain, Rechtstexte, Version der Rechtstexte | `env/*.json` (`SITE_URL`, `LEGAL_*`, `TERMS_VERSION`) |
Die Bundle-ID lässt sich nach der ersten Store-Veröffentlichung nicht mehr ändern.
## Hinweise
- Generierte Dateien (`*.g.dart`, `lib/l10n/app_localizations*.dart`) werden mit eingecheckt; nach Änderungen an `@riverpod`-Code oder ARB-Dateien neu erzeugen (`build_runner`, `flutter gen-l10n`).
- Web-URLs ohne `#` (`usePathUrlStrategy`): der Webserver braucht eine SPA-Rewrite-Regel (alle Pfade auf `index.html`).

37
app/analysis_options.yaml Normal file
View file

@ -0,0 +1,37 @@
include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- build/**
- android/**
- ios/**
- web/**
- "**/*.g.dart"
- "**/*.freezed.dart"
- lib/l10n/app_localizations*.dart
language:
strict-casts: true
strict-inference: true
strict-raw-types: true
errors:
missing_required_param: error
missing_return: error
todo: ignore
linter:
rules:
avoid_print: true
always_declare_return_types: true
prefer_single_quotes: true
prefer_const_constructors: true
prefer_const_declarations: true
prefer_final_locals: true
prefer_final_in_for_each: true
unawaited_futures: true
require_trailing_commas: true
sort_pub_dependencies: false
directives_ordering: true
avoid_dynamic_calls: true
use_super_parameters: true
cancel_subscriptions: true
close_sinks: true

14
app/android/.gitignore vendored Normal file
View file

@ -0,0 +1,14 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
.cxx/
# Remember to never publicly share your keystore.
# See https://flutter.dev/to/reference-keystore
key.properties
**/*.keystore
**/*.jks

View file

@ -0,0 +1,49 @@
plugins {
id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "work.cshomelabs.shuffle"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "work.cshomelabs.shuffle"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
// Uses the version code from pubspec.yaml. When using split APKs, 1000 * ABI_VERSION
// is added automatically by Flutter. (https://developer.android.com/studio/build/configure-apk-splits#configure-APK-versions)
// You can force using the value of versionCode by specifying the `-P force-version-code-ignoring-abi=true`
// flag during build.
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
kotlin {
compilerOptions {
jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17
}
}
flutter {
source = "../.."
}

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -0,0 +1,47 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required in release builds (the debug manifest adds it only for debug/profile). -->
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:label="Shuffle"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>

View file

@ -0,0 +1,5 @@
package work.cshomelabs.shuffle
import io.flutter.embedding.android.FlutterActivity
class MainActivity : FlutterActivity()

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View file

@ -0,0 +1,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -0,0 +1,24 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View file

@ -0,0 +1,6 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
# This newDsl flag was added by the Flutter template
android.newDsl=false
# This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-all.zip

View file

@ -0,0 +1,26 @@
pluginManagement {
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "9.1.0" apply false
id("org.jetbrains.kotlin.android") version "2.4.0" apply false
}
include(":app")

9
app/env/ci.example.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"APP_ENV": "ci",
"SUPABASE_URL": "http://localhost:54321",
"SUPABASE_ANON_KEY": "public-anon-key-placeholder",
"SITE_URL": "http://localhost:3000",
"LEGAL_PRIVACY_URL": "https://shuffle.cshomelabs.work/legal/privacy",
"LEGAL_TERMS_URL": "https://shuffle.cshomelabs.work/legal/terms",
"TERMS_VERSION": "2026-11-draft"
}

9
app/env/dev.example.json vendored Normal file
View file

@ -0,0 +1,9 @@
{
"APP_ENV": "dev",
"SUPABASE_URL": "http://localhost:54321",
"SUPABASE_ANON_KEY": "public-anon-key-placeholder",
"SITE_URL": "http://localhost:3000",
"LEGAL_PRIVACY_URL": "https://shuffle.cshomelabs.work/legal/privacy",
"LEGAL_TERMS_URL": "https://shuffle.cshomelabs.work/legal/terms",
"TERMS_VERSION": "2026-11-draft"
}

34
app/ios/.gitignore vendored Normal file
View file

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
</dict>
</plist>

View file

@ -0,0 +1 @@
#include "Generated.xcconfig"

View file

@ -0,0 +1 @@
#include "Generated.xcconfig"

View file

@ -0,0 +1,651 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */; };
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 97C146E61CF9000F007C117D /* Project object */;
proxyType = 1;
remoteGlobalIDString = 97C146ED1CF9000F007C117D;
remoteInfo = Runner;
};
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
331C8082294A63A400263BE5 /* RunnerTests */ = {
isa = PBXGroup;
children = (
331C807B294A618700263BE5 /* RunnerTests.swift */,
);
path = RunnerTests;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
331C8081294A63A400263BE5 /* RunnerTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
7884E8672EC3CC0400C636F2 /* SceneDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
331C8080294A63A400263BE5 /* RunnerTests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
);
buildRules = (
);
dependencies = (
331C8086294A63A400263BE5 /* PBXTargetDependency */,
);
name = RunnerTests;
productName = RunnerTests;
productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
packageProductDependencies = (
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */,
);
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "";
TargetAttributes = {
331C8080294A63A400263BE5 = {
CreatedOnToolsVersion = 14.0;
TestTargetID = 97C146ED1CF9000F007C117D;
};
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
de,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
packageReferences = (
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */,
);
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
331C8080294A63A400263BE5 /* RunnerTests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
331C807F294A63A400263BE5 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
331C807D294A63A400263BE5 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
7884E8682EC3CC0700C636F2 /* SceneDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
331C8086294A63A400263BE5 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 97C146ED1CF9000F007C117D /* Runner */;
targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = KSX6GJY9C5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Debug;
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Release;
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = NO;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
STRING_CATALOG_GENERATE_SYMBOLS = YES;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = KSX6GJY9C5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = KSX6GJY9C5;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = work.cshomelabs.shuffle;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
331C8088294A63A400263BE5 /* Debug */,
331C8089294A63A400263BE5 /* Release */,
331C808A294A63A400263BE5 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
/* Begin XCLocalSwiftPackageReference section */
781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = {
isa = XCLocalSwiftPackageReference;
relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage;
};
/* End XCLocalSwiftPackageReference section */
/* Begin XCSwiftPackageProductDependency section */
78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = {
isa = XCSwiftPackageProductDependency;
productName = FlutterGeneratedPluginSwiftPackage;
};
/* End XCSwiftPackageProductDependency section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1510"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<PreActions>
<ExecutionAction
ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
<ActionContent
title = "Run Prepare Flutter Framework Script"
scriptText = "/bin/sh &quot;$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh&quot; prepare&#10;">
<EnvironmentBuildable>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</EnvironmentBuildable>
</ActionContent>
</ExecutionAction>
</PreActions>
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO"
parallelizable = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "331C8080294A63A400263BE5"
BuildableName = "RunnerTests.xctest"
BlueprintName = "RunnerTests"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View file

@ -0,0 +1,16 @@
import Flutter
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}

View file

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View file

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View file

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

75
app/ios/Runner/Info.plist Normal file
View file

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleLocalizations</key>
<array>
<string>de</string>
<string>en</string>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Shuffle</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Shuffle</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View file

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View file

@ -0,0 +1,6 @@
import Flutter
import UIKit
class SceneDelegate: FlutterSceneDelegate {
}

View file

@ -0,0 +1,12 @@
import Flutter
import UIKit
import XCTest
class RunnerTests: XCTestCase {
func testExample() {
// If you add code to the Runner application, consider adding tests here.
// See https://developer.apple.com/documentation/xctest for more information about using XCTest.
}
}

5
app/l10n.yaml Normal file
View file

@ -0,0 +1,5 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-class: AppLocalizations
nullable-getter: false

31
app/lib/app.dart Normal file
View file

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shuffle/core/l10n/app_locale.dart';
import 'package:shuffle/core/routing/app_router.dart';
import 'package:shuffle/core/theme/app_theme.dart';
import 'package:shuffle/l10n/app_localizations.dart';
class ShuffleApp extends ConsumerWidget {
const ShuffleApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
return MaterialApp.router(
onGenerateTitle: (context) => AppLocalizations.of(context).appTitle,
theme: AppTheme.light(),
darkTheme: AppTheme.dark(),
themeMode: ThemeMode.system,
routerConfig: ref.watch(appRouterProvider),
locale: ref.watch(appLocaleProvider),
supportedLocales: AppLocalizations.supportedLocales,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
localeResolutionCallback: (locale, supported) => resolveAppLocale(locale),
);
}
}

View file

@ -0,0 +1,103 @@
/// Laufzeitkonfiguration der App.
///
/// Die Werte kommen zur Build-Zeit aus `--dart-define-from-file=env/<stage>.json`
/// (siehe `app/env/*.example.json`). Secrets gehören nicht ins Repo.
class AppConfig {
const AppConfig({
required this.environment,
required this.supabaseUrl,
required this.supabaseAnonKey,
required this.siteUrl,
required this.legalPrivacyUrl,
required this.legalTermsUrl,
required this.termsVersion,
});
/// Liest die Konfiguration aus den `--dart-define`-Werten.
///
/// `String.fromEnvironment` muss mit konstanten Namen aufgerufen werden;
/// deshalb steht jeder Schlüssel hier einzeln.
factory AppConfig.fromEnvironment() => const AppConfig(
environment: String.fromEnvironment(
keyAppEnv,
defaultValue: defaultEnvironment,
),
supabaseUrl: String.fromEnvironment(keySupabaseUrl),
supabaseAnonKey: String.fromEnvironment(keySupabaseAnonKey),
siteUrl: String.fromEnvironment(keySiteUrl, defaultValue: defaultSiteUrl),
legalPrivacyUrl: String.fromEnvironment(
keyLegalPrivacyUrl,
defaultValue: defaultLegalPrivacyUrl,
),
legalTermsUrl: String.fromEnvironment(
keyLegalTermsUrl,
defaultValue: defaultLegalTermsUrl,
),
termsVersion: String.fromEnvironment(
keyTermsVersion,
defaultValue: defaultTermsVersion,
),
);
/// Baut die Konfiguration aus einer Map (Tests, Tooling). Fehlende
/// optionale Werte erhalten dieselben Platzhalter wie [fromEnvironment].
factory AppConfig.fromMap(Map<String, String> values) => AppConfig(
environment: values[keyAppEnv] ?? defaultEnvironment,
supabaseUrl: values[keySupabaseUrl] ?? '',
supabaseAnonKey: values[keySupabaseAnonKey] ?? '',
siteUrl: values[keySiteUrl] ?? defaultSiteUrl,
legalPrivacyUrl: values[keyLegalPrivacyUrl] ?? defaultLegalPrivacyUrl,
legalTermsUrl: values[keyLegalTermsUrl] ?? defaultLegalTermsUrl,
termsVersion: values[keyTermsVersion] ?? defaultTermsVersion,
);
static const keyAppEnv = 'APP_ENV';
static const keySupabaseUrl = 'SUPABASE_URL';
static const keySupabaseAnonKey = 'SUPABASE_ANON_KEY';
static const keySiteUrl = 'SITE_URL';
static const keyLegalPrivacyUrl = 'LEGAL_PRIVACY_URL';
static const keyLegalTermsUrl = 'LEGAL_TERMS_URL';
static const keyTermsVersion = 'TERMS_VERSION';
// Platzhalter (Spec #1 § 5.1), bis Chris die echten Werte festlegt.
static const defaultEnvironment = 'dev';
static const defaultSiteUrl = 'http://localhost:3000';
static const defaultLegalPrivacyUrl =
'https://shuffle.cshomelabs.work/legal/privacy';
static const defaultLegalTermsUrl =
'https://shuffle.cshomelabs.work/legal/terms';
static const defaultTermsVersion = '2026-11-draft';
/// Name der Stage (`dev`, `ci`, `staging`, `prod`).
final String environment;
final String supabaseUrl;
final String supabaseAnonKey;
/// Öffentliche Basis-URL der Web-App (Ziel der Mail-Links).
final String siteUrl;
final String legalPrivacyUrl;
final String legalTermsUrl;
/// Version der Rechtstexte, die bei der Registrierung gespeichert wird.
final String termsVersion;
/// Namen der Pflichtwerte, die fehlen oder leer sind.
List<String> get missingKeys => [
if (supabaseUrl.isEmpty) keySupabaseUrl,
if (supabaseAnonKey.isEmpty) keySupabaseAnonKey,
];
bool get isValid => missingKeys.isEmpty;
/// Wirft einen [StateError], wenn Pflichtwerte fehlen.
void validate() {
final missing = missingKeys;
if (missing.isNotEmpty) {
throw StateError(
'Missing app configuration: ${missing.join(', ')}. '
'Start the app with --dart-define-from-file=env/<stage>.json '
'(see app/env/dev.example.json).',
);
}
}
}

View file

@ -0,0 +1,8 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shuffle/core/config/app_config.dart';
part 'app_config_provider.g.dart';
/// Konfiguration aus `--dart-define-from-file`. In Tests überschreiben.
@Riverpod(keepAlive: true)
AppConfig appConfig(Ref ref) => AppConfig.fromEnvironment();

View file

@ -0,0 +1,55 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_config_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Konfiguration aus `--dart-define-from-file`. In Tests überschreiben.
@ProviderFor(appConfig)
final appConfigProvider = AppConfigProvider._();
/// Konfiguration aus `--dart-define-from-file`. In Tests überschreiben.
final class AppConfigProvider
extends $FunctionalProvider<AppConfig, AppConfig, AppConfig>
with $Provider<AppConfig> {
/// Konfiguration aus `--dart-define-from-file`. In Tests überschreiben.
AppConfigProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'appConfigProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$appConfigHash();
@$internal
@override
$ProviderElement<AppConfig> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
AppConfig create(Ref ref) {
return appConfig(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(AppConfig value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<AppConfig>(value),
);
}
}
String _$appConfigHash() => r'87f9d20a2d92252672162b2ce97547b7b2479239';

View file

@ -0,0 +1,24 @@
import 'package:flutter/foundation.dart';
import 'package:shuffle/core/logging/app_logger.dart';
/// Leitet nicht abgefangene Framework- und Plattformfehler an den Logger.
void installGlobalErrorHandlers({AppLogger? logger}) {
final log = logger ?? AppLogger('GlobalError');
FlutterError.onError = (details) {
log.error(
'Flutter framework error',
error: details.exception,
stackTrace: details.stack,
);
// Im Debug-Modus zusätzlich die Standardausgabe der Konsole.
if (kDebugMode) {
FlutterError.presentError(details);
}
};
PlatformDispatcher.instance.onError = (error, stackTrace) {
log.error('Uncaught platform error', error: error, stackTrace: stackTrace);
return true;
};
}

View file

@ -0,0 +1,51 @@
import 'package:flutter/widgets.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shuffle/l10n/app_localizations.dart';
part 'app_locale.g.dart';
/// Fallback-Sprache, wenn die Gerätesprache nicht unterstützt wird
/// (QUE-05 Kriterium 4).
const fallbackLocale = Locale('en');
/// Bildet eine Gerätesprache auf eine unterstützte Sprache ab (de/en, sonst en).
///
/// Wird auch als `localeResolutionCallback` genutzt: gen-l10n sortiert die
/// unterstützten Sprachen alphabetisch, Flutter würde sonst auf Deutsch fallen.
Locale resolveAppLocale(Locale? deviceLocale) {
if (deviceLocale == null) {
return fallbackLocale;
}
for (final supported in AppLocalizations.supportedLocales) {
if (supported.languageCode == deviceLocale.languageCode) {
return Locale(supported.languageCode);
}
}
return fallbackLocale;
}
/// Aktive Oberflächensprache: Gerätesprache (de/en), sonst Englisch.
///
/// Folgt Änderungen der Gerätesprache. Eine manuelle Wahl folgt mit KTO-11.
@Riverpod(keepAlive: true)
class AppLocale extends _$AppLocale {
@override
Locale build() {
final binding = WidgetsBinding.instance;
final observer = _LocaleObserver(
(locales) => state = resolveAppLocale(locales?.firstOrNull),
);
binding.addObserver(observer);
ref.onDispose(() => binding.removeObserver(observer));
return resolveAppLocale(binding.platformDispatcher.locales.firstOrNull);
}
}
class _LocaleObserver with WidgetsBindingObserver {
_LocaleObserver(this._onChanged);
final void Function(List<Locale>? locales) _onChanged;
@override
void didChangeLocales(List<Locale>? locales) => _onChanged(locales);
}

View file

@ -0,0 +1,74 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_locale.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Aktive Oberflächensprache: Gerätesprache (de/en), sonst Englisch.
///
/// Folgt Änderungen der Gerätesprache. Eine manuelle Wahl folgt mit KTO-11.
@ProviderFor(AppLocale)
final appLocaleProvider = AppLocaleProvider._();
/// Aktive Oberflächensprache: Gerätesprache (de/en), sonst Englisch.
///
/// Folgt Änderungen der Gerätesprache. Eine manuelle Wahl folgt mit KTO-11.
final class AppLocaleProvider extends $NotifierProvider<AppLocale, Locale> {
/// Aktive Oberflächensprache: Gerätesprache (de/en), sonst Englisch.
///
/// Folgt Änderungen der Gerätesprache. Eine manuelle Wahl folgt mit KTO-11.
AppLocaleProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'appLocaleProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$appLocaleHash();
@$internal
@override
AppLocale create() => AppLocale();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(Locale value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<Locale>(value),
);
}
}
String _$appLocaleHash() => r'69f10aec719d677951a31d0551fe3bc440e4fbcd';
/// Aktive Oberflächensprache: Gerätesprache (de/en), sonst Englisch.
///
/// Folgt Änderungen der Gerätesprache. Eine manuelle Wahl folgt mit KTO-11.
abstract class _$AppLocale extends $Notifier<Locale> {
Locale build();
@$mustCallSuper
@override
WhenComplete runBuild() {
final ref = this.ref as $Ref<Locale, Locale>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<Locale, Locale>,
Locale,
Object?,
Object?
>;
return element.handleCreate(ref, build);
}
}

View file

@ -0,0 +1,8 @@
import 'package:flutter/widgets.dart';
import 'package:shuffle/l10n/app_localizations.dart';
export 'package:shuffle/l10n/app_localizations.dart';
extension AppLocalizationsContext on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this);
}

View file

@ -0,0 +1,61 @@
import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
/// Zentrales Logger-Utility (Wrapper um `package:logging`).
///
/// Regeln: kein `print`, keine personenbezogenen Daten (E-Mail, Namen,
/// Passwörter, Tokens) in Nachrichten oder Fehlerobjekten.
class AppLogger {
AppLogger(String name) : _logger = Logger(name);
final Logger _logger;
void debug(String message) => _logger.fine(message);
void info(String message) => _logger.info(message);
void warning(String message, {Object? error, StackTrace? stackTrace}) =>
_logger.warning(message, error, stackTrace);
void error(String message, {Object? error, StackTrace? stackTrace}) =>
_logger.severe(message, error, stackTrace);
static StreamSubscription<LogRecord>? _subscription;
/// Richtet die Ausgabe ein. Release: ab INFO, sonst alles.
///
/// Idempotent: Wiederholte Aufrufe aktualisieren nur den Level und
/// registrieren keinen weiteren Listener.
///
/// Hinweis: `developer.log` hat in Release-Builds praktisch keine sichtbare
/// Wirkung; produktives Crash-/Error-Reporting folgt in einem eigenen Task.
static void configure({Level? level}) {
Logger.root.level = level ?? (kReleaseMode ? Level.INFO : Level.ALL);
_subscription ??= Logger.root.onRecord.listen(_write);
}
/// Aktiver Listener (nur für Tests).
@visibleForTesting
static StreamSubscription<LogRecord>? get subscription => _subscription;
/// Entfernt den Listener wieder (nur für Tests).
@visibleForTesting
static Future<void> resetForTesting() async {
await _subscription?.cancel();
_subscription = null;
}
static void _write(LogRecord record) {
developer.log(
record.message,
time: record.time,
level: record.level.value,
name: record.loggerName,
error: record.error,
stackTrace: record.stackTrace,
);
}
}

View file

@ -0,0 +1,40 @@
/// Einfaches Ergebnis-Typ für erwartbare Fehler (kein zusätzliches Paket).
sealed class Result<T, E> {
const Result();
const factory Result.success(T value) = Success<T, E>;
const factory Result.failure(E error) = Failure<T, E>;
bool get isSuccess => this is Success<T, E>;
bool get isFailure => this is Failure<T, E>;
/// Wert bei Erfolg, sonst `null`.
T? get valueOrNull => switch (this) {
Success<T, E>(:final value) => value,
Failure<T, E>() => null,
};
/// Fehler bei Misserfolg, sonst `null`.
E? get errorOrNull => switch (this) {
Success<T, E>() => null,
Failure<T, E>(:final error) => error,
};
R when<R>({
required R Function(T value) success,
required R Function(E error) failure,
}) => switch (this) {
Success<T, E>(:final value) => success(value),
Failure<T, E>(:final error) => failure(error),
};
}
final class Success<T, E> extends Result<T, E> {
const Success(this.value);
final T value;
}
final class Failure<T, E> extends Result<T, E> {
const Failure(this.error);
final E error;
}

View file

@ -0,0 +1,28 @@
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shuffle/core/routing/app_routes.dart';
import 'package:shuffle/core/routing/not_found_screen.dart';
import 'package:shuffle/core/routing/redirect_source.dart';
import 'package:shuffle/features/home/presentation/home_screen.dart';
part 'app_router.g.dart';
@Riverpod(keepAlive: true)
GoRouter appRouter(Ref ref) {
final source = ref.watch(redirectSourceProvider);
final router = GoRouter(
initialLocation: AppRoutes.home,
refreshListenable: source.changes,
redirect: (context, state) => source.redirect(state),
errorBuilder: (context, state) => const NotFoundScreen(),
routes: [
GoRoute(
path: AppRoutes.home,
builder: (context, state) => const HomeScreen(),
),
],
);
ref.onDispose(router.dispose);
return router;
}

View file

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'app_router.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(appRouter)
final appRouterProvider = AppRouterProvider._();
final class AppRouterProvider
extends $FunctionalProvider<GoRouter, GoRouter, GoRouter>
with $Provider<GoRouter> {
AppRouterProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'appRouterProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$appRouterHash();
@$internal
@override
$ProviderElement<GoRouter> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
GoRouter create(Ref ref) {
return appRouter(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(GoRouter value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<GoRouter>(value),
);
}
}
String _$appRouterHash() => r'c3ba8cecc011ab4327ceb2284a58118355176803';

View file

@ -0,0 +1,4 @@
/// Pfade der App. Weitere Routen kommen mit den Features.
abstract final class AppRoutes {
static const home = '/';
}

View file

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:shuffle/core/l10n/l10n.dart';
import 'package:shuffle/core/routing/app_routes.dart';
/// 404-Seite für unbekannte Pfade.
class NotFoundScreen extends StatelessWidget {
const NotFoundScreen({super.key});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
key: const Key('not_found_screen'),
appBar: AppBar(title: Text(l10n.notFoundTitle)),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(l10n.notFoundMessage, textAlign: TextAlign.center),
const SizedBox(height: 16),
FilledButton(
key: const Key('not_found_home_button'),
onPressed: () => context.go(AppRoutes.home),
child: Text(l10n.notFoundBackHome),
),
],
),
),
),
);
}
}

View file

@ -0,0 +1,33 @@
import 'package:flutter/foundation.dart';
import 'package:go_router/go_router.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'redirect_source.g.dart';
/// Austauschbare Quelle für Redirect-Entscheidungen des Routers.
///
/// Das Fundament liefert [NoRedirectSource]. Die Auth-Story (#1) überschreibt
/// [redirectSource] mit einer Implementierung, die den Anmeldestatus kennt.
abstract interface class RedirectSource {
/// Löst eine Neubewertung der Redirects aus (z. B. Anmeldestatus ändert sich).
Listenable get changes;
/// Zielpfad für den Redirect oder `null`, wenn die Route bleibt.
String? redirect(GoRouterState state);
}
/// Standard: kein Redirect, keine Änderungen.
class NoRedirectSource implements RedirectSource {
const NoRedirectSource();
@override
Listenable get changes => _never;
@override
String? redirect(GoRouterState state) => null;
static final _never = ValueNotifier<int>(0);
}
@Riverpod(keepAlive: true)
RedirectSource redirectSource(Ref ref) => const NoRedirectSource();

View file

@ -0,0 +1,51 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'redirect_source.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
@ProviderFor(redirectSource)
final redirectSourceProvider = RedirectSourceProvider._();
final class RedirectSourceProvider
extends $FunctionalProvider<RedirectSource, RedirectSource, RedirectSource>
with $Provider<RedirectSource> {
RedirectSourceProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'redirectSourceProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$redirectSourceHash();
@$internal
@override
$ProviderElement<RedirectSource> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
RedirectSource create(Ref ref) {
return redirectSource(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(RedirectSource value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<RedirectSource>(value),
);
}
}
String _$redirectSourceHash() => r'c568056f573be8b72071c02e497ace86ebf04dc8';

View file

@ -0,0 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_web_plugins/url_strategy.dart';
/// Web: saubere Pfade ohne `#`. Auf Mobilgeräten ohne Wirkung.
///
/// Der Webserver braucht dafür eine SPA-Rewrite-Regel (alle Pfade auf
/// `index.html`).
void configurePathUrlStrategy() {
if (kIsWeb) {
usePathUrlStrategy();
}
}

View file

@ -0,0 +1,9 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
part 'supabase_client_provider.g.dart';
/// Der initialisierte Supabase-Client (`Supabase.initialize` läuft in `main`).
/// In Tests mit einem Mock überschreiben.
@Riverpod(keepAlive: true)
SupabaseClient supabaseClient(Ref ref) => Supabase.instance.client;

View file

@ -0,0 +1,58 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'supabase_client_provider.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint, type=warning
/// Der initialisierte Supabase-Client (`Supabase.initialize` läuft in `main`).
/// In Tests mit einem Mock überschreiben.
@ProviderFor(supabaseClient)
final supabaseClientProvider = SupabaseClientProvider._();
/// Der initialisierte Supabase-Client (`Supabase.initialize` läuft in `main`).
/// In Tests mit einem Mock überschreiben.
final class SupabaseClientProvider
extends $FunctionalProvider<SupabaseClient, SupabaseClient, SupabaseClient>
with $Provider<SupabaseClient> {
/// Der initialisierte Supabase-Client (`Supabase.initialize` läuft in `main`).
/// In Tests mit einem Mock überschreiben.
SupabaseClientProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'supabaseClientProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$supabaseClientHash();
@$internal
@override
$ProviderElement<SupabaseClient> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
SupabaseClient create(Ref ref) {
return supabaseClient(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(SupabaseClient value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<SupabaseClient>(value),
);
}
}
String _$supabaseClientHash() => r'3db2a4c212c7f24cea9810e376225aa1a6cab012';

View file

@ -0,0 +1,62 @@
import 'package:flutter/widgets.dart';
/// Spacing tokens (dp) on a 4dp grid.
abstract final class AppSpacing {
static const double xxs = 2;
static const double xs = 4;
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double xl = 24;
static const double xxl = 32;
static const double xxxl = 48;
/// Minimum size of interactive targets (WCAG 2.5.5 / Material: 48dp).
static const double minTouchTarget = 48;
/// Page padding per width class.
static EdgeInsets pagePadding(double width) => EdgeInsets.symmetric(
horizontal: width >= AppBreakpoints.expanded
? xxl
: width >= AppBreakpoints.medium
? xl
: lg,
vertical: lg,
);
}
/// Corner radius tokens (dp).
abstract final class AppRadius {
static const double sm = 8;
static const double md = 12;
static const double lg = 16;
static const double xl = 28;
static BorderRadius get smAll => BorderRadius.circular(sm);
static BorderRadius get mdAll => BorderRadius.circular(md);
static BorderRadius get lgAll => BorderRadius.circular(lg);
}
/// Width breakpoints: compact < 600, medium 600-1023, expanded >= 1024.
abstract final class AppBreakpoints {
static const double medium = 600;
static const double expanded = 1024;
/// Max content width for forms/text on wide screens.
static const double maxContentWidth = 560;
/// Max width of the centered auth card (Spec #1 § 6) from the medium class on.
static const double authCardMaxWidth = 480;
}
enum AppWidthClass {
compact,
medium,
expanded;
static AppWidthClass of(double width) => width >= AppBreakpoints.expanded
? expanded
: width >= AppBreakpoints.medium
? medium
: compact;
}

View file

@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import 'package:shuffle/core/theme/app_spacing.dart';
/// Material 3 theme base of the design system (see docs/design-system.md).
///
/// Colors come from [ColorScheme.fromSeed]; the contrast of all core pairs is
/// verified in `test/core/theme/app_theme_test.dart` (WCAG AA, >= 4.5:1 for
/// text, >= 3:1 for the focus indicator and input borders).
abstract final class AppTheme {
static const Color seed = Color(0xFF4F46E5);
static ThemeData light() => _build(Brightness.light);
static ThemeData dark() => _build(Brightness.dark);
static ColorScheme _scheme(Brightness brightness) {
final base = ColorScheme.fromSeed(seedColor: seed, brightness: brightness);
// Tone 50/60 outlines keep >= 3:1 against the surface (WCAG 1.4.11);
// the M3 default outline is slightly weaker in light mode.
return base.copyWith(
outline: brightness == Brightness.light
? const Color(0xFF6B6A7A)
: const Color(0xFF9A99AB),
);
}
static ThemeData _build(Brightness brightness) {
final scheme = _scheme(brightness);
final textTheme = Typography.material2021(colorScheme: scheme).black
.apply(bodyColor: scheme.onSurface, displayColor: scheme.onSurface);
final themedText = brightness == Brightness.dark
? Typography.material2021(colorScheme: scheme).white.apply(
bodyColor: scheme.onSurface,
displayColor: scheme.onSurface,
)
: textTheme;
const minSize = Size(AppSpacing.minTouchTarget, AppSpacing.minTouchTarget);
final shape = RoundedRectangleBorder(borderRadius: AppRadius.mdAll);
// Visible keyboard focus ring (web/desktop), contrast >= 3:1.
final focusSide = WidgetStateProperty.resolveWith<BorderSide?>(
(states) => states.contains(WidgetState.focused)
? BorderSide(color: scheme.primary, width: 3)
: null,
);
// The primary ring is invisible on the primary fill of a FilledButton;
// onSurface contrasts with both the fill and the surrounding surface.
final filledFocusSide = WidgetStateProperty.resolveWith<BorderSide?>(
(states) => states.contains(WidgetState.focused)
? BorderSide(color: scheme.onSurface, width: 3)
: null,
);
OutlineInputBorder border(Color color, double width) => OutlineInputBorder(
borderRadius: AppRadius.mdAll,
borderSide: BorderSide(color: color, width: width),
);
return ThemeData(
useMaterial3: true,
colorScheme: scheme,
textTheme: themedText,
visualDensity: VisualDensity.standard,
materialTapTargetSize: MaterialTapTargetSize.padded,
scaffoldBackgroundColor: scheme.surface,
focusColor: scheme.primary.withValues(alpha: 0.16),
dividerTheme: DividerThemeData(color: scheme.outlineVariant),
appBarTheme: AppBarTheme(
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
centerTitle: false,
titleTextStyle: themedText.titleLarge,
),
cardTheme: CardThemeData(
elevation: 0,
color: scheme.surfaceContainerLow,
shape: RoundedRectangleBorder(
borderRadius: AppRadius.lgAll,
side: BorderSide(color: scheme.outlineVariant),
),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
minimumSize: minSize,
shape: shape,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xl),
).copyWith(side: filledFocusSide),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
minimumSize: minSize,
shape: shape,
).copyWith(side: focusSide),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style:
OutlinedButton.styleFrom(
minimumSize: minSize,
shape: shape,
side: BorderSide(color: scheme.outline),
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.xl),
).copyWith(
side: WidgetStateProperty.resolveWith<BorderSide?>(
(states) => states.contains(WidgetState.focused)
? BorderSide(color: scheme.primary, width: 3)
: BorderSide(color: scheme.outline),
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
minimumSize: minSize,
shape: shape,
padding: const EdgeInsets.symmetric(horizontal: AppSpacing.lg),
).copyWith(side: focusSide),
),
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(minimumSize: minSize),
),
checkboxTheme: CheckboxThemeData(
materialTapTargetSize: MaterialTapTargetSize.padded,
side: BorderSide(color: scheme.outline, width: 2),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: scheme.surfaceContainerHighest,
contentPadding: const EdgeInsets.symmetric(
horizontal: AppSpacing.lg,
vertical: AppSpacing.md,
),
border: border(scheme.outline, 1),
enabledBorder: border(scheme.outline, 1),
focusedBorder: border(scheme.primary, 3),
errorBorder: border(scheme.error, 2),
focusedErrorBorder: border(scheme.error, 3),
errorStyle: themedText.bodySmall?.copyWith(color: scheme.error),
helperStyle: themedText.bodySmall?.copyWith(
color: scheme.onSurfaceVariant,
),
),
snackBarTheme: SnackBarThemeData(
behavior: SnackBarBehavior.floating,
backgroundColor: scheme.inverseSurface,
contentTextStyle: themedText.bodyMedium?.copyWith(
color: scheme.onInverseSurface,
),
actionTextColor: scheme.inversePrimary,
shape: RoundedRectangleBorder(borderRadius: AppRadius.smAll),
showCloseIcon: true,
closeIconColor: scheme.onInverseSurface,
),
);
}
}

View file

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
import 'package:shuffle/core/l10n/l10n.dart';
/// Platzhalter-Startseite. Wird durch die Katalogsuche bzw. QUE-09 ersetzt.
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return Scaffold(
key: const Key('home_screen'),
appBar: AppBar(title: Text(l10n.appTitle)),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text(
l10n.homePlaceholderMessage,
key: const Key('home_placeholder_message'),
textAlign: TextAlign.center,
),
),
),
);
}
}

8
app/lib/l10n/app_de.arb Normal file
View file

@ -0,0 +1,8 @@
{
"@@locale": "de",
"appTitle": "Shuffle",
"homePlaceholderMessage": "Willkommen bei Shuffle. Der Brettspielkatalog folgt in Kürze.",
"notFoundTitle": "Seite nicht gefunden",
"notFoundMessage": "Diese Seite existiert nicht oder wurde verschoben.",
"notFoundBackHome": "Zurück zum Start"
}

23
app/lib/l10n/app_en.arb Normal file
View file

@ -0,0 +1,23 @@
{
"@@locale": "en",
"appTitle": "Shuffle",
"@appTitle": {
"description": "Name of the app"
},
"homePlaceholderMessage": "Welcome to Shuffle. The board game catalog is coming soon.",
"@homePlaceholderMessage": {
"description": "Placeholder text on the start page until real content exists"
},
"notFoundTitle": "Page not found",
"@notFoundTitle": {
"description": "Title of the 404 page"
},
"notFoundMessage": "This page does not exist or has been moved.",
"@notFoundMessage": {
"description": "Explanation on the 404 page"
},
"notFoundBackHome": "Back to start",
"@notFoundBackHome": {
"description": "Button on the 404 page that navigates to the start page"
}
}

View file

@ -0,0 +1,164 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:intl/intl.dart' as intl;
import 'app_localizations_de.dart';
import 'app_localizations_en.dart';
// ignore_for_file: type=lint
/// Callers can lookup localized strings with an instance of AppLocalizations
/// returned by `AppLocalizations.of(context)`.
///
/// Applications need to include `AppLocalizations.delegate()` in their app's
/// `localizationDelegates` list, and the locales they support in the app's
/// `supportedLocales` list. For example:
///
/// ```dart
/// import 'l10n/app_localizations.dart';
///
/// return MaterialApp(
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
/// supportedLocales: AppLocalizations.supportedLocales,
/// home: MyApplicationHome(),
/// );
/// ```
///
/// ## Update pubspec.yaml
///
/// Please make sure to update your pubspec.yaml to include the following
/// packages:
///
/// ```yaml
/// dependencies:
/// # Internationalization support.
/// flutter_localizations:
/// sdk: flutter
/// intl: any # Use the pinned version from flutter_localizations
///
/// # Rest of dependencies
/// ```
///
/// ## iOS Applications
///
/// iOS applications define key application metadata, including supported
/// locales, in an Info.plist file that is built into the application bundle.
/// To configure the locales supported by your app, you’ll need to edit this
/// file.
///
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
/// Then, in the Project Navigator, open the Info.plist file under the Runner
/// project’s Runner folder.
///
/// Next, select the Information Property List item, select Add Item from the
/// Editor menu, then select Localizations from the pop-up menu.
///
/// Select and expand the newly-created Localizations item then, for each
/// locale your application supports, add a new item and select the locale
/// you wish to add from the pop-up menu in the Value field. This list should
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
/// property.
abstract class AppLocalizations {
AppLocalizations(String locale)
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
final String localeName;
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations)!;
}
static const LocalizationsDelegate<AppLocalizations> delegate =
_AppLocalizationsDelegate();
/// A list of this localizations delegate along with the default localizations
/// delegates.
///
/// Returns a list of localizations delegates containing this delegate along with
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
/// and GlobalWidgetsLocalizations.delegate.
///
/// Additional delegates can be added by appending to this list in
/// MaterialApp. This list does not have to be used at all if a custom list
/// of delegates is preferred or required.
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
<LocalizationsDelegate<dynamic>>[
delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
];
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[
Locale('de'),
Locale('en'),
];
/// Name of the app
///
/// In en, this message translates to:
/// **'Shuffle'**
String get appTitle;
/// Placeholder text on the start page until real content exists
///
/// In en, this message translates to:
/// **'Welcome to Shuffle. The board game catalog is coming soon.'**
String get homePlaceholderMessage;
/// Title of the 404 page
///
/// In en, this message translates to:
/// **'Page not found'**
String get notFoundTitle;
/// Explanation on the 404 page
///
/// In en, this message translates to:
/// **'This page does not exist or has been moved.'**
String get notFoundMessage;
/// Button on the 404 page that navigates to the start page
///
/// In en, this message translates to:
/// **'Back to start'**
String get notFoundBackHome;
}
class _AppLocalizationsDelegate
extends LocalizationsDelegate<AppLocalizations> {
const _AppLocalizationsDelegate();
@override
Future<AppLocalizations> load(Locale locale) {
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
}
@override
bool isSupported(Locale locale) =>
<String>['de', 'en'].contains(locale.languageCode);
@override
bool shouldReload(_AppLocalizationsDelegate old) => false;
}
AppLocalizations lookupAppLocalizations(Locale locale) {
// Lookup logic when only language code is specified.
switch (locale.languageCode) {
case 'de':
return AppLocalizationsDe();
case 'en':
return AppLocalizationsEn();
}
throw FlutterError(
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
'an issue with the localizations generation tool. Please file an issue '
'on GitHub with a reproducible sample app and the gen-l10n configuration '
'that was used.',
);
}

View file

@ -0,0 +1,28 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for German (`de`).
class AppLocalizationsDe extends AppLocalizations {
AppLocalizationsDe([String locale = 'de']) : super(locale);
@override
String get appTitle => 'Shuffle';
@override
String get homePlaceholderMessage =>
'Willkommen bei Shuffle. Der Brettspielkatalog folgt in Kürze.';
@override
String get notFoundTitle => 'Seite nicht gefunden';
@override
String get notFoundMessage =>
'Diese Seite existiert nicht oder wurde verschoben.';
@override
String get notFoundBackHome => 'Zurück zum Start';
}

View file

@ -0,0 +1,27 @@
// ignore: unused_import
import 'package:intl/intl.dart' as intl;
import 'app_localizations.dart';
// ignore_for_file: type=lint
/// The translations for English (`en`).
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get appTitle => 'Shuffle';
@override
String get homePlaceholderMessage =>
'Welcome to Shuffle. The board game catalog is coming soon.';
@override
String get notFoundTitle => 'Page not found';
@override
String get notFoundMessage => 'This page does not exist or has been moved.';
@override
String get notFoundBackHome => 'Back to start';
}

23
app/lib/main.dart Normal file
View file

@ -0,0 +1,23 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shuffle/app.dart';
import 'package:shuffle/core/config/app_config.dart';
import 'package:shuffle/core/error/global_error_handler.dart';
import 'package:shuffle/core/logging/app_logger.dart';
import 'package:shuffle/core/routing/url_strategy.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
AppLogger.configure();
installGlobalErrorHandlers();
configurePathUrlStrategy();
final config = AppConfig.fromEnvironment()..validate();
await Supabase.initialize(
url: config.supabaseUrl,
publishableKey: config.supabaseAnonKey,
);
runApp(const ProviderScope(child: ShuffleApp()));
}

999
app/pubspec.lock Normal file
View file

@ -0,0 +1,999 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "1c5591cc6f252d5f440d96ab87c03e43504be552a3fa49d7cb7563f04c51fb0a"
url: "https://pub.dev"
source: hosted
version: "111.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "6c39bd5486a310e56217bed7cb1691377959f31be4227e41750107db7e6dd55c"
url: "https://pub.dev"
source: hosted
version: "14.7.0"
analyzer_buffer:
dependency: transitive
description:
name: analyzer_buffer
sha256: f890a2e708e38fbf4238ad2f9fd3d506538acdddb0cd8eb2c3e8c7bd17784b57
url: "https://pub.dev"
source: hosted
version: "0.3.4"
app_links:
dependency: transitive
description:
name: app_links
sha256: "08ba88bf19d9edea33130cb71c8ced2cb982b4e00b82efef4a434d1357bc69c3"
url: "https://pub.dev"
source: hosted
version: "7.2.2"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: "9ccb5802085a1e57bb07b6256fd1d61a78c7ec104944d4965e62938b4fefd9cb"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "7546f09a6e93f4a2df2fe2bd40a5c6c64310ac461b036d82b43033be7a59f809"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: "382d32c6f486f15b1d7f5d6a37a70a481a4fc08382eef419d373169921a3e765"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
args:
dependency: transitive
description:
name: args
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
url: "https://pub.dev"
source: hosted
version: "2.7.0"
async:
dependency: transitive
description:
name: async
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
url: "https://pub.dev"
source: hosted
version: "2.13.1"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
build:
dependency: transitive
description:
name: build
sha256: "8a5c5761af8e31748bba3c82f68925ace40f3225c3eea25be9beb57eca7cd7a8"
url: "https://pub.dev"
source: hosted
version: "4.0.11"
build_config:
dependency: transitive
description:
name: build_config
sha256: d466ed2dc9c6cd1d169948879b84ee061eb5e22c64a7c6089879c6296d272a8d
url: "https://pub.dev"
source: hosted
version: "1.3.3"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "39ab65f7c60c19af64b9ef96e6a5d3ab48355f7dddfd98a9079d55a8cb6628fc"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "6feadd32d90a68253c1f7b4fcff76adb4b41ad32a53f8bad835dc65f01ac9dc5"
url: "https://pub.dev"
source: hosted
version: "2.16.2"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "44922ecebd8e3d0cef743dadd3b54df4e87e2a4c52a29465a1872aa9e192caa8"
url: "https://pub.dev"
source: hosted
version: "5.1.2"
built_value:
dependency: transitive
description:
name: built_value
sha256: f87ea98192116f7093cb214551ce1929caae0681fdba282b3d8b4462adee7bb7
url: "https://pub.dev"
source: hosted
version: "8.13.0"
characters:
dependency: transitive
description:
name: characters
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.1"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
clock:
dependency: transitive
description:
name: clock
sha256: e51d50bca3217c9a9fa2b41a30e4a38971133f5f9ec7a3d57bae095007f1d28e
url: "https://pub.dev"
source: hosted
version: "1.1.3"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "811709c1badd02fc054039a13f66e0db1961865f9a314175f68c3eaeebec786f"
url: "https://pub.dev"
source: hosted
version: "4.13.0"
collection:
dependency: transitive
description:
name: collection
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
url: "https://pub.dev"
source: hosted
version: "1.19.1"
convert:
dependency: transitive
description:
name: convert
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
url: "https://pub.dev"
source: hosted
version: "3.1.2"
crypto:
dependency: transitive
description:
name: crypto
sha256: e3626ae6270383c9e3a549f34c9e4cd26c09ae6a9adaab2bf4737a00340efdc4
url: "https://pub.dev"
source: hosted
version: "3.0.8"
cupertino_ui:
dependency: transitive
description:
name: cupertino_ui
sha256: e003b87afde18cb396ec15928c7894063d64af04462c68df9a1404c461316dd2
url: "https://pub.dev"
source: hosted
version: "1.1.5"
dart_jsonwebtoken:
dependency: transitive
description:
name: dart_jsonwebtoken
sha256: ad84e60181696513d04d5f2078e0bbc20365b911f46f647797317414bdc88fbe
url: "https://pub.dev"
source: hosted
version: "3.4.1"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: a0e4f65339b31eb6a9e6a12e14b4682a48d46fbc54a82f4a702396fbdbac63a7
url: "https://pub.dev"
source: hosted
version: "3.1.14"
fake_async:
dependency: transitive
description:
name: fake_async
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
ffi:
dependency: transitive
description:
name: ffi
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
file:
dependency: transitive
description:
name: file
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev"
source: hosted
version: "7.0.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
url: "https://pub.dev"
source: hosted
version: "1.1.1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_riverpod:
dependency: "direct main"
description:
name: flutter_riverpod
sha256: "2b7f9d2a3c730ac1a98221b420cef966b990fca7ab546ede324642ca57a533e3"
url: "https://pub.dev"
source: hosted
version: "3.4.3"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
freezed:
dependency: "direct dev"
description:
name: freezed
sha256: cb910646cb09265d49077b63b600ef1f5a718c1d6031836432237ddeabae1b12
url: "https://pub.dev"
source: hosted
version: "4.0.3"
freezed_annotation:
dependency: "direct main"
description:
name: freezed_annotation
sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
functions_client:
dependency: transitive
description:
name: functions_client
sha256: "931e822688d2e97867b2300e2fdbb08c9db73f8653fc83efc7a259d2e0cca769"
url: "https://pub.dev"
source: hosted
version: "2.7.1"
glob:
dependency: transitive
description:
name: glob
sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb"
url: "https://pub.dev"
source: hosted
version: "2.2.0"
go_router:
dependency: "direct main"
description:
name: go_router
sha256: f1c2d3c2b0b2096b435d7af033dd4e586fa31f51d988c5985e64495cd926d872
url: "https://pub.dev"
source: hosted
version: "18.1.2"
gotrue:
dependency: transitive
description:
name: gotrue
sha256: cca816226824160e40d149816fd89740cc64dda1eaa3d9161e334330b18bba89
url: "https://pub.dev"
source: hosted
version: "2.27.3"
graphs:
dependency: transitive
description:
name: graphs
sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
http:
dependency: transitive
description:
name: http
sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412"
url: "https://pub.dev"
source: hosted
version: "1.6.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "361668bdd71f0ae2dc6d17b75a593520f3c414af6534f9eed1aa72049dd71d18"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
intl:
dependency: "direct main"
description:
name: intl
sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867"
url: "https://pub.dev"
source: hosted
version: "0.20.3"
io:
dependency: transitive
description:
name: io
sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
jni:
dependency: transitive
description:
name: jni
sha256: d8df6dab6847855512fe44dd9032b0bbe34dda7bf7597e41d30750879779813f
url: "https://pub.dev"
source: hosted
version: "1.1.1"
jni_util:
dependency: transitive
description:
name: jni_util
sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80"
url: "https://pub.dev"
source: hosted
version: "4.12.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: cc7822761b40be9237bcaae62b755ffe5453ceff96a0001f1f5f2439251c66f1
url: "https://pub.dev"
source: hosted
version: "6.15.2"
leak_tracker:
dependency: transitive
description:
name: leak_tracker
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
lints:
dependency: transitive
description:
name: lints
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
listen:
dependency: transitive
description:
name: listen
sha256: "47501a08016a43fcad79252439d723f50f14f88fa7bfd8a177e0a417e5c9e1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
logging:
dependency: "direct main"
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher:
dependency: transitive
description:
name: matcher
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
url: "https://pub.dev"
source: hosted
version: "0.12.20"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.13.0"
material_ui:
dependency: transitive
description:
name: material_ui
sha256: feb289df1e5f9ed9522f9fe3d909432a7f7bc76943f16abad09f80a16cd25196
url: "https://pub.dev"
source: hosted
version: "1.9.0"
meta:
dependency: transitive
description:
name: meta
sha256: c82594181e3312f3d0695fc95aaaf7758d75b8d4ae2bbecf223b9fd5109a059d
url: "https://pub.dev"
source: hosted
version: "1.18.3"
mime:
dependency: transitive
description:
name: mime
sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6
url: "https://pub.dev"
source: hosted
version: "2.1.0"
mockito:
dependency: transitive
description:
name: mockito
sha256: "6c1970612452260366064d4df80fdb3eece1ec5e52610ae34a0e51f2f3d8e64f"
url: "https://pub.dev"
source: hosted
version: "5.8.1"
mocktail:
dependency: "direct dev"
description:
name: mocktail
sha256: "5e1bf53cc7baa8062a33b84424deb61513858ea05c601b8509e683815b5914aa"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
package_config:
dependency: transitive
description:
name: package_config
sha256: ffcf4cf3d6c0b74ac43708d9f56625506e8a68aa935abe9d267a7330f320eb5d
url: "https://pub.dev"
source: hosted
version: "3.0.0"
passkeys_platform_interface:
dependency: transitive
description:
name: passkeys_platform_interface
sha256: "5fca044a6f1a3c5abff645ca5a5417b613c130d1c4e08513ba9182c7f4834619"
url: "https://pub.dev"
source: hosted
version: "2.10.0"
path:
dependency: transitive
description:
name: path
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
url: "https://pub.dev"
source: hosted
version: "2.3.0"
platform:
dependency: transitive
description:
name: platform
sha256: a36d119c13416516a7b5913fbe8af8531e11633d784c550b2125f76c758524ec
url: "https://pub.dev"
source: hosted
version: "3.2.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
url: "https://pub.dev"
source: hosted
version: "2.1.8"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
pool:
dependency: transitive
description:
name: pool
sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c"
url: "https://pub.dev"
source: hosted
version: "1.5.3"
postgrest:
dependency: transitive
description:
name: postgrest
sha256: b7f957273ba6a1c26cefa987ac524f4138e9ab7542fbb9e6abdfddacb9895a21
url: "https://pub.dev"
source: hosted
version: "2.9.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: "9ac81c01ffd6a9cf9b2713288834fbaaf6aebde05ffdf02d380e68b8e06f27d8"
url: "https://pub.dev"
source: hosted
version: "1.7.0"
realtime_client:
dependency: transitive
description:
name: realtime_client
sha256: "2ef680d83ba2c0f76019d16194ca7067fa2458b2d4091b52323a9ca4a964fd40"
url: "https://pub.dev"
source: hosted
version: "2.13.2"
riverpod:
dependency: transitive
description:
name: riverpod
sha256: "484dfc873ea4c4f4240e5635444fa066f87b07862b7279ebd004a2b71fba4b7a"
url: "https://pub.dev"
source: hosted
version: "3.4.3"
riverpod_analyzer_utils:
dependency: transitive
description:
name: riverpod_analyzer_utils
sha256: bb13cb3985815840cc103eda1027158358c5fdc00f308fc6f76574d0594107af
url: "https://pub.dev"
source: hosted
version: "1.0.0-dev.12"
riverpod_annotation:
dependency: "direct main"
description:
name: riverpod_annotation
sha256: "322e56ad198928ef26ba240f85421bfff2493bd65665c887b1299e6386b6206d"
url: "https://pub.dev"
source: hosted
version: "4.0.7"
riverpod_generator:
dependency: "direct dev"
description:
name: riverpod_generator
sha256: c94dcb8b60ff4805d2bb8cacca969c48237bcb563e2ab0b5afb50204470c7636
url: "https://pub.dev"
source: hosted
version: "4.0.9"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "1e85e0dccf5b4a016077fea7dfbb4a7cb39c759ac1596fcf60c06deadb7da91f"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "33b38b07d9ac7d16aff0426c61418752fd3e7a8c7dc8be786c875d6ce673b002"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "2ec3934efa51e46117f23031cc141b8fc878e8525b94ec1ea4f7f586cf1b47ea"
url: "https://pub.dev"
source: hosted
version: "2.5.7"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
name: shelf
sha256: e75e9cf311d7fd712048038cc17c25199c38536c20330d53e7db058b543d5a48
url: "https://pub.dev"
source: hosted
version: "1.4.3"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "833ea7d15b5be79949d3d6c077da0267a0bd93c16897b36a771395a91e63086e"
url: "https://pub.dev"
source: hosted
version: "4.3.1"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "279126bce72de3f2b7d4756408a4157988cf6bb4815be551bf55851aa00e9984"
url: "https://pub.dev"
source: hosted
version: "1.4.0"
source_span:
dependency: transitive
description:
name: source_span
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
url: "https://pub.dev"
source: hosted
version: "1.10.2"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490"
url: "https://pub.dev"
source: hosted
version: "1.12.2"
state_notifier:
dependency: transitive
description:
name: state_notifier
sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb
url: "https://pub.dev"
source: hosted
version: "1.0.0"
storage_client:
dependency: transitive
description:
name: storage_client
sha256: b951281b8e2b293460f0e6cc4e866f2546681390f0e37d24fbbd7b4a0deb27a3
url: "https://pub.dev"
source: hosted
version: "2.8.1"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: a00e5f18bffc764f923e7dec1038527f7fe7a1791361a7117f0358193f13d53a
url: "https://pub.dev"
source: hosted
version: "2.1.2"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
url: "https://pub.dev"
source: hosted
version: "1.4.1"
supabase:
dependency: transitive
description:
name: supabase
sha256: "123fe08792b24e5e7c4b9a01145de8e95ebaa9183cd5f3c9d48dd2ff890f28a6"
url: "https://pub.dev"
source: hosted
version: "2.16.5"
supabase_common:
dependency: transitive
description:
name: supabase_common
sha256: a7961a44953cf8d9da049381fc3377516348255042fbb12b9d4f8540816f2fe9
url: "https://pub.dev"
source: hosted
version: "0.1.2"
supabase_flutter:
dependency: "direct main"
description:
name: supabase_flutter
sha256: "48bbde226e68bdb293020859a1bfa42d0f3bd2239992fba0ec4f9194cbb842e6"
url: "https://pub.dev"
source: hosted
version: "2.18.3"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
url: "https://pub.dev"
source: hosted
version: "1.2.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
url: "https://pub.dev"
source: hosted
version: "0.7.12"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: e8cc955c97e06df9879140a1fe301f3d493f01b7c62dec7f46834be31d7e9fda
url: "https://pub.dev"
source: hosted
version: "6.3.3"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "611e87fb320b70d1dd721dc46af89c98aceccea9b31fde49e084591414e0c610"
url: "https://pub.dev"
source: hosted
version: "6.3.33"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "8faa1aab294f1ab4040b43660c887b0418d5fa4f0cffef76a484e6aa1092eb4a"
url: "https://pub.dev"
source: hosted
version: "6.4.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "10f86fef4c2c43563fa6c211ff9cf757adf4d3ab762c56bd430664a947d70cd0"
url: "https://pub.dev"
source: hosted
version: "3.2.3"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "5e835a3b869c2d70325349c81c5a45c28e20791265b67b2669da6b08c5cd5201"
url: "https://pub.dev"
source: hosted
version: "3.2.6"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34"
url: "https://pub.dev"
source: hosted
version: "2.4.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "6c5ad3f22cd4c38e089b81963b3cd7bb83b111b2df5dce008bb066162f42e429"
url: "https://pub.dev"
source: hosted
version: "3.1.6"
uuid:
dependency: transitive
description:
name: uuid
sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd"
url: "https://pub.dev"
source: hosted
version: "4.6.0"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: fa49e1674cb84acb900141714e6c6e1f9ae099827c24d141430ff6819db4e1bc
url: "https://pub.dev"
source: hosted
version: "2.4.4"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "89b11d62e1040ee681af3b4f1c97a803ecbf010c6e8b9c5896ee5b08c3bf7b78"
url: "https://pub.dev"
source: hosted
version: "15.4.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
web:
dependency: transitive
description:
name: web
sha256: e119e82ac4795c7925103b7cf3768191f33941b99e7ccffbd6c645201d9f606b
url: "https://pub.dev"
source: hosted
version: "1.1.2"
web_socket:
dependency: transitive
description:
name: web_socket
sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
url: "https://pub.dev"
source: hosted
version: "3.0.3"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea
url: "https://pub.dev"
source: hosted
version: "3.1.4"
yet_another_json_isolate:
dependency: transitive
description:
name: yet_another_json_isolate
sha256: eaa26beb5990b25a49d942374fd5a0c5aa67a837e03b14b4c26134aaa1ed01a9
url: "https://pub.dev"
source: hosted
version: "2.1.1"
sdks:
dart: ">=3.13.7 <4.0.0"
flutter: ">=3.47.0"

39
app/pubspec.yaml Normal file
View file

@ -0,0 +1,39 @@
name: shuffle
description: "Shuffle – Brettspiel-App für iOS, Android und Web."
publish_to: 'none'
version: 0.1.0+1
environment:
sdk: ^3.13.7
# Versionen gepinnt laut Spec #1 § 5.7 (Fundament-Issue #31).
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
flutter_web_plugins:
sdk: flutter
flutter_riverpod: 3.4.3
riverpod_annotation: 4.0.7
go_router: 18.1.2
supabase_flutter: 2.18.3
freezed_annotation: 3.1.0
json_annotation: 4.12.0
logging: 1.3.0
intl: 0.20.3
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: 6.0.0
build_runner: 2.16.2
riverpod_generator: 4.0.9
freezed: 4.0.3
json_serializable: 6.15.2
mocktail: 1.0.5
flutter:
uses-material-design: true
generate: true

Some files were not shown because too many files have changed in this diff Show more