first commit

This commit is contained in:
2026-07-21 16:50:58 +02:00
commit 256599626e
407 changed files with 30489 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# Contribuer à Ben-to
## Workflow
1. Ouvrir ou **référencer une issue** GitHub (comportement attendu, contexte, critères dacceptation).
2. Créer une branche depuis `main` : `feat/issue-123-courte-description`, `fix/issue-123-…`, etc.
3. Implémenter en respectant la structure **feature-first** (`packages/features/*`, `packages/shared/*`) et les règles du dépôt (`.cursor/rules/`).
4. **Scénarios Gherkin** (`specs/features/*.feature`) : tout tag `@int-*` doit apparaître dans un fichier `*.int.test.ts` correspondant (voir `npm run check:gherkin-scenarios`).
5. **Imports explicites** : pas de fichier barrel `packages/**/src/index.ts` ; utiliser les sous-chemins documentés dans les `package.json` `exports` et les alias Vite (`@ben-to/.../domain`, etc.).
6. Avant la PR : `npm run check:quality` et `npm run test:ci`.
7. **Issue GitHub** : la laisser **ouverte** jusquau merge de la PR. Inclure dans le **message de commit** (recommandé, dernière ligne) `Closes #N` / `Fixes #N` pour que GitHub ferme lissue automatiquement au merge ; éviter de fermer lissue manuellement avant la fusion.
### Hook pre-commit (tests unitaires)
Après `npm install`, Husky installe un hook qui exécute `vitest related` sur les fichiers `.ts` / `.tsx` stagés (projet **unit** uniquement).
Contournement exceptionnel (hotfix documenté en PR) : `git commit --no-verify`.
## Qualité et couverture
- Les seuils de couverture Vitest sappliquent aux sources sous `packages/features/**` et `packages/shared/**` (voir `vitest.config.ts`). Le front web est couvert par les **e2e**, **Axe** et **Lighthouse**.
- La CI publie un **commentaire de synthèse** sur les PR (couverture, audit npm, Axe, Lighthouse, timings navigation).
## GitHub
- Activez **Code scanning** avec CodeQL si vous voulez les résultats SARIF dans longlet Sécurité ; puis retirez `upload: false` dans `.github/workflows/security.yml` (voir [docs/CODE_SCANNING.md](docs/CODE_SCANNING.md)).
- Protégez la branche `main` : CI obligatoire, revues si vous travaillez en équipe.
## Mobile Android
Les commandes `mobile:*` se lancent depuis la **racine** du monorepo (voir [README.md](README.md)). La CI exécute `assembleDebug` pour détecter les régressions Gradle/Capacitor.
+27
View File
@@ -0,0 +1,27 @@
FROM node:22-alpine AS build
WORKDIR /app
ENV CI=1
COPY package*.json ./
COPY apps/web/package.json apps/web/package.json
COPY packages/features/builder/package.json packages/features/builder/package.json
COPY packages/features/recipes/package.json packages/features/recipes/package.json
COPY packages/shared/i18n/package.json packages/shared/i18n/package.json
COPY packages/shared/privacy/package.json packages/shared/privacy/package.json
RUN npm ci --no-audit --no-fund
COPY . .
RUN node scripts/build-recipes.mjs
# Vite : les VITE_* sont injectées au build (CapRover : build args ou CI avec --build-arg).
ARG VITE_MATOMO_URL=
ARG VITE_MATOMO_SITE_ID=
ENV VITE_MATOMO_URL=${VITE_MATOMO_URL}
ENV VITE_MATOMO_SITE_ID=${VITE_MATOMO_SITE_ID}
RUN npm run build:web
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/apps/web/dist ./dist
RUN npm install -g serve@14.2.4 --omit=dev --no-audit --no-fund
EXPOSE 3000
CMD ["serve", "-s", "dist", "-l", "3000"]
+156
View File
@@ -0,0 +1,156 @@
# Ben-to
Constructeur de bento pixel-art, multi-plateforme (web + Android + iOS).
## Stack
- Web: SolidJS (Vite)
- Mobile: Capacitor
- Données: catalogue embarqué, progression locale (offline, sans compte cloud en V1)
- Qualité: Vitest + Playwright + Axe + Lighthouse
Partage des URLs recette (`/r/…`) et métadonnées Open Graph : voir [docs/seo-recipe-sharing.md](docs/seo-recipe-sharing.md).
Matomo (analytics + bandeau cookies) : voir [docs/matomo-analytics.md](docs/matomo-analytics.md) et `apps/web/.env.example`.
## Démarrage
```bash
npm install
npm run pipeline:recipes
npm run dev:web
```
Les **recettes** (fichiers `*.lang.bentext`), les **couvertures** (`ressources/public/`, placeholders dans `ressources/recipe-placeholder-covers/`), les **sprites ingrédients** (`ressources/public/ingredients.png`, `ingredient-sprites.bentext`, source Aseprite sous `ressources/source/`) sont **versionnés dans ce dépôt** — pas de synchronisation avec un serveur de contenus externe.
Après modification des sources, régénérer le catalogue :
```bash
npm run pipeline:recipes
```
Règles d’édition des fichiers bentext : `.cursor/rules/recipes-bentext.mdc`.
## Tester l'app web
À exécuter depuis la **racine** du dépôt.
**Même chaîne que la CI** (ESLint + TypeScript, Vitest avec **couverture** sur les packages métier, Playwright + Axe, pas de duplication unit/int séparée dans ce script) :
```bash
npm run pipeline:recipes # régénère le catalogue si besoin (dev / e2e)
npm run test:ci
```
Voir [CONTRIBUTING.md](CONTRIBUTING.md) (hook pre-commit, tickets, Gherkin `@int-*`).
Les **e2e** (`npm run test:e2e` ou via `test:ci`) sappuient sur `playwright.config.ts` : le serveur Vite est démarré avec `npm run dev:web` si aucun n’écoute déjà sur le port (voir `reuseExistingServer`).
Pour **régénérer les GIF** du tour daccueil (fichiers dans `apps/web/public/tour/`, hors `test:ci`) : `npm run tour:capture` — nécessite `ffmpeg` sur la machine (`playwright.tour-capture.config.ts`).
**Contrôles ciblés sur le paquet `apps/web`** (lint + types du front uniquement) :
```bash
npm run lint -w apps/web
npm run typecheck -w apps/web
```
**Build de production** du front :
```bash
npm run pipeline:recipes
npm run build:web
```
## Tester sur un téléphone Android (Capacitor)
Le shell natif est dans `**apps/mobile**` et embarque le build statique `**apps/web/dist**` (voir `apps/mobile/capacitor.config.ts`).
Un correctif **patch-package** (`patches/@capacitor-community+sqlite+8.1.0.patch`) remplace `proguard-android.txt` par `proguard-android-optimize.txt` dans le plugin SQLite (exigence R8 / AGP récents). Il est réappliqué à chaque `**npm install`** via le script `**postinstall`**.
**Important — répertoire courant** : `pipeline:recipes`, `build:web` et les scripts `mobile:`* sont définis dans le `**package.json` à la racine** du dépôt (`ben-to/`, là où se trouve ce README). Si vous lancez `npm run …` depuis `**apps/mobile`**, npm cherche ces scripts dans le workspace mobile et échoue (`Missing script`). Vérifiez avec `ls package.json` : vous devez voir le manifest racine, pas celui du sous-dossier.
### Prérequis
- [Android Studio](https://developer.android.com/studio) (SDK + outils plate-forme).
- Sur le téléphone : **Options pour les développeurs** activées, puis **Débogage USB** (câble USB) ou **Débogage sans fil** (WiFi) selon votre appareil.
### Première fois : ajouter la plateforme Android
Si le dossier `**apps/mobile/android/`** nexiste pas encore dans votre clone :
```bash
cd apps/mobile
npx cap add android
cd ../..
```
### Build du web, sync Capacitor, lancer sur lappareil
**Toujours depuis la racine du dépôt** (puis Android Studio souvre sur `apps/mobile/android`) :
```bash
npm install
npm run pipeline:recipes
npm run build:web
npm run mobile:sync
npm run mobile:open:android
```
(`mobile:sync` / `mobile:open:android` appellent le workspace `**@ben-to/mobile**` ; évite les erreurs du type `No workspaces found` si le flag `-w` ne cible pas le bon nom.)
Dans Android Studio : choisir votre téléphone comme cible, puis **Run** (▶).
En ligne de commande (appareil déjà autorisé / `adb devices` OK), **toujours depuis la racine** :
```bash
npm run mobile:run:android
```
### Modifier le front sans tout régénérer (optionnel)
Pour charger lUI depuis votre PC sur le réseau local (évite un `build:web` à chaque changement), vous pouvez temporairement renseigner dans `apps/mobile/capacitor.config.ts` un bloc `server: { url: "http://VOTRE_IP_LAN:3000", cleartext: true }` (à retirer avant release), puis lancer le front avec l’écoute réseau :
```bash
npm run dev:web -- --host 0.0.0.0
```
Adaptez le pare-feu / le même WiFi entre le PC et le téléphone.
## Qualité obligatoire
```bash
npm run check:quality
npm run test:unit
npm run test:integration
npm run test:e2e
```
## CI / Sécurité
- `.github/workflows/ci.yml` — qualité, couverture, e2e + artefacts qualité, Lighthouse, **assembleDebug Android**, rapport PR
- `.github/workflows/security.yml` — CodeQL, `npm audit`, Gitleaks ([activation upload SARIF](docs/CODE_SCANNING.md))
- `.github/workflows/dependency-review.yml` — revue des dépendances sur les PR (selon options du dépôt)
- `.github/workflows/mobile-release.yml`
## Publication V1 (web + Android)
Checklist gel release : [docs/V1_LAUNCH_CHECKLIST.md](docs/V1_LAUNCH_CHECKLIST.md).
## Déploiement CapRover
- Le dépôt contient `Dockerfile` et `captain-definition` pour un déploiement via CapRover (hors GitHub Actions).
## Ticketing GitHub
Création rapide des issues MVP :
```bash
./scripts/github/create-issues.sh kazerlelutin/ben-to
```
Tickets post-MVP (builder SOLID, suppression des barrels `index`, SEO OG recettes) : gabarits sous [.github/ISSUE_TEMPLATE/](.github/ISSUE_TEMPLATE/) (`04``06`), corps dans [docs/tickets/](docs/tickets/), création en masse après `gh auth login` :
```bash
./scripts/github/create-post-mvp-issues.sh
```
+91
View File
@@ -0,0 +1,91 @@
# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore
# Built application files
*.apk
*.aar
*.ap_
*.aab
# Files for the ART/Dalvik VM
*.dex
# Java class files
*.class
# Generated files
bin/
gen/
out/
# Uncomment the following line in case you need and you don't have the release build type files in your app
# release/
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Proguard folder generated by Eclipse
proguard/
# Log Files
*.log
# Android Studio Navigation editor temp files
.navigation/
# Android Studio captures folder
captures/
# IntelliJ / Android Studio
*.iml
.idea/
# Keystore files
# Uncomment the following lines if you do not want to check your keystore files in.
#*.jks
#*.keystore
# External native build folder generated in Android Studio 2.2 and later
.externalNativeBuild
.cxx/
# Google Services (e.g. APIs or Firebase)
# google-services.json
# Freeline
freeline.py
freeline/
freeline_project_description.json
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output
fastlane/readme.md
# Version control
vcs.xml
# lint
lint/intermediates/
lint/generated/
lint/outputs/
lint/tmp/
# lint/reports/
# Android Profiling
*.hprof
# Cordova plugins for Capacitor
capacitor-cordova-android-plugins
# Copied web assets
app/src/main/assets/public
# Generated Config files
app/src/main/assets/capacitor.config.json
app/src/main/assets/capacitor.plugins.json
app/src/main/res/xml/config.xml
+2
View File
@@ -0,0 +1,2 @@
/build/*
!/build/.npmkeep
+74
View File
@@ -0,0 +1,74 @@
apply plugin: 'com.android.application'
/** Fichier optionnel `android/keystore.properties` + chemin `storeFile` — voir `keystore.properties.example`. */
def keystorePropertiesFile = rootProject.file("keystore.properties")
def keystoreProperties = new Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
namespace "com.kazerlelutin.bento"
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "com.kazerlelutin.bento"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
aaptOptions {
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
// Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61
ignoreAssetsPattern '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~'
}
}
signingConfigs {
release {
if (keystorePropertiesFile.exists()) {
keyAlias keystoreProperties["keyAlias"]
keyPassword keystoreProperties["keyPassword"]
storeFile rootProject.file(keystoreProperties["storeFile"])
storePassword keystoreProperties["storePassword"]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
if (keystorePropertiesFile.exists()) {
signingConfig signingConfigs.release
}
}
}
}
repositories {
flatDir{
dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs'
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion"
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
implementation project(':capacitor-android')
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
implementation project(':capacitor-cordova-android-plugins')
}
apply from: 'capacitor.build.gradle'
try {
def servicesJSON = file('google-services.json')
if (servicesJSON.text) {
apply plugin: 'com.google.gms.google-services'
}
} catch(Exception e) {
logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work")
}
@@ -0,0 +1,20 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_21
targetCompatibility JavaVersion.VERSION_21
}
}
apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
dependencies {
implementation project(':capacitor-share')
implementation project(':capacitor-community-sqlite')
}
if (hasProperty('postBuildExtras')) {
postBuildExtras()
}
+251
View File
@@ -0,0 +1,251 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
androidx.activity:activity:1.7.0=debugAndroidTestCompileClasspath
androidx.activity:activity:1.9.2=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-experimental:1.4.0=debugAndroidTestCompileClasspath
androidx.annotation:annotation-experimental:1.4.1=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation-jvm:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.annotation:annotation:1.8.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.appcompat:appcompat-resources:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.appcompat:appcompat:1.7.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-common:2.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.arch.core:core-runtime:2.1.0=debugAndroidTestCompileClasspath
androidx.arch.core:core-runtime:2.2.0=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.biometric:biometric:1.1.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection-jvm:1.4.2=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.collection:collection:1.1.0=debugAndroidTestCompileClasspath
androidx.collection:collection:1.4.2=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.concurrent:concurrent-futures-ktx:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.concurrent:concurrent-futures:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.coordinatorlayout:coordinatorlayout:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core-ktx:1.15.0=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core-ktx:1.2.0=debugAndroidTestCompileClasspath
androidx.core:core-splashscreen:1.0.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.core:core:1.13.0=debugAndroidTestCompileClasspath
androidx.core:core:1.15.0=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.cursoradapter:cursoradapter:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.customview:customview:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.drawerlayout:drawerlayout:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.emoji2:emoji2-views-helper:1.3.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.emoji2:emoji2:1.3.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.fragment:fragment:1.5.4=debugAndroidTestCompileClasspath
androidx.fragment:fragment:1.8.4=debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.interpolator:interpolator:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-common:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-livedata-core:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-livedata:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-process:2.6.2=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-runtime:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.lifecycle:lifecycle-viewmodel:2.6.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.loader:loader:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.profileinstaller:profileinstaller:1.3.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.resourceinspection:resourceinspection-annotation:1.0.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.room:room-common:2.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.room:room-runtime:2.6.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.savedstate:savedstate:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.security:security-crypto:1.1.0-alpha06=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.sqlite:sqlite-framework:2.4.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.sqlite:sqlite:2.4.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.startup:startup-runtime:1.1.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.test.espresso:espresso-core:3.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test.espresso:espresso-idling-resource:3.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test.ext:junit:1.2.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test.services:storage:1.5.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:core:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:monitor:1.7.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.test:runner:1.6.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
androidx.tracing:tracing:1.2.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable-animated:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.vectordrawable:vectordrawable:1.1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.versionedparcelable:versionedparcelable:1.1.1=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.viewpager:viewpager:1.0.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
androidx.webkit:webkit:1.12.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
com.android.tools.analytics-library:protos:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools.analytics-library:shared:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools.analytics-library:tracker:32.2.0=androidLintTool
com.android.tools.build:aapt2-proto:9.2.0-15009934=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools.build:builder-model:9.2.0=androidLintTool
com.android.tools.build:manifest-merger:32.2.0=androidLintTool
com.android.tools.ddms:ddmlib:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.android.tools.emulator:proto:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control
com.android.tools.external.com-intellij:intellij-core:32.2.0=androidLintTool
com.android.tools.external.com-intellij:kotlin-compiler:32.2.0=androidLintTool
com.android.tools.external.org-jetbrains:uast:32.2.0=androidLintTool
com.android.tools.layoutlib:layoutlib-api:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools.lint:lint-api:32.2.0=androidLintTool
com.android.tools.lint:lint-checks:32.2.0=androidLintTool
com.android.tools.lint:lint-gradle:32.2.0=androidLintTool
com.android.tools.lint:lint-model:32.2.0=androidLintTool
com.android.tools.lint:lint-typedef-remover:32.2.0=androidLintTool
com.android.tools.lint:lint:32.2.0=androidLintTool
com.android.tools.utp:android-device-provider-ddmlib-proto:32.2.0=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-gradle-work-action
com.android.tools.utp:android-device-provider-ddmlib:32.2.0=unified-test-platform-android-device-provider-ddmlib
com.android.tools.utp:android-test-plugin-host-additional-test-output-proto:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-host-additional-test-output:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output
com.android.tools.utp:android-test-plugin-host-apk-installer-proto:32.2.0=unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-host-apk-installer:32.2.0=unified-test-platform-android-test-plugin-host-apk-installer
com.android.tools.utp:android-test-plugin-host-coverage-proto:32.2.0=unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-host-coverage:32.2.0=unified-test-platform-android-test-plugin-host-coverage
com.android.tools.utp:android-test-plugin-host-device-info-proto:32.2.0=unified-test-platform-android-test-plugin-host-device-info
com.android.tools.utp:android-test-plugin-host-device-info:32.2.0=unified-test-platform-android-test-plugin-host-device-info
com.android.tools.utp:android-test-plugin-host-emulator-control-proto:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-host-emulator-control:32.2.0=unified-test-platform-android-test-plugin-host-emulator-control
com.android.tools.utp:android-test-plugin-host-logcat-proto:32.2.0=unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-host-logcat:32.2.0=unified-test-platform-android-test-plugin-host-logcat
com.android.tools.utp:android-test-plugin-result-listener-gradle-proto:32.2.0=unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.android.tools.utp:android-test-plugin-result-listener-gradle:32.2.0=unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools.utp:gradle-work-action:32.2.0=unified-test-platform-gradle-work-action
com.android.tools.utp:utp-common:32.2.0=unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat
com.android.tools:annotations:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.android.tools:common:32.2.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.android.tools:dvlib:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools:play-sdk-proto:32.2.0=androidLintTool
com.android.tools:repository:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools:sdk-common:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.android.tools:sdklib:32.2.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.android:annotations:4.1.1.4=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
com.google.api.grpc:proto-google-common-protos:2.17.0=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core
com.google.api.grpc:proto-google-common-protos:2.48.0=unified-test-platform-android-test-plugin-host-emulator-control
com.google.auto.service:auto-service-annotations:1.1.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.auto.service:auto-service:1.1.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.auto:auto-common:1.2.1=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.code.findbugs:jsr305:3.0.2=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.google.code.gson:gson:2.10.1=unified-test-platform-core,unified-test-platform-gradle-work-action
com.google.code.gson:gson:2.11.0=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.code.gson:gson:2.8.9=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath,unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher
com.google.crypto.tink:tink-android:1.8.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
com.google.crypto.tink:tink:1.18.0=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action
com.google.dagger:dagger:2.48=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action
com.google.errorprone:error_prone_annotations:2.18.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
com.google.errorprone:error_prone_annotations:2.23.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
com.google.errorprone:error_prone_annotations:2.28.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.google.errorprone:error_prone_annotations:2.30.0=unified-test-platform-android-test-plugin-host-emulator-control
com.google.guava:failureaccess:1.0.1=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
com.google.guava:failureaccess:1.0.2=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.google.guava:guava:32.0.1-jre=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
com.google.guava:guava:33.3.1-jre=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.google.guava:listenablefuture:1.0=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.google.j2objc:j2objc-annotations:2.8=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
com.google.j2objc:j2objc-annotations:3.0.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.google.jimfs:jimfs:1.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.protobuf:protobuf-java-util:3.22.3=unified-test-platform-core
com.google.protobuf:protobuf-java-util:4.28.3=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.google.protobuf:protobuf-java:3.25.5=androidLintTool
com.google.protobuf:protobuf-java:4.28.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.google.protobuf:protobuf-kotlin:4.28.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.google.testing.platform:android-device-provider-local:0.0.9-alpha04=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle
com.google.testing.platform:android-driver-instrumentation:0.0.9-alpha04=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control
com.google.testing.platform:android-test-plugin:0.0.9-alpha04=unified-test-platform-android-test-plugin
com.google.testing.platform:core-proto:0.0.9-alpha04=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
com.google.testing.platform:core:0.0.9-alpha04=unified-test-platform-core
com.google.testing.platform:launcher:0.0.9-alpha04=unified-test-platform-gradle-work-action,unified-test-platform-launcher
com.sun.istack:istack-commons-runtime:3.0.8=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
com.sun.xml.fastinfoset:FastInfoset:1.2.16=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
commons-codec:commons-codec:1.17.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
commons-io:commons-io:2.16.1=androidLintTool,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
commons-logging:commons-logging:1.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
io.grpc:grpc-api:1.57.2=unified-test-platform-core
io.grpc:grpc-api:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-context:1.57.2=unified-test-platform-core
io.grpc:grpc-context:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-core:1.57.2=unified-test-platform-core
io.grpc:grpc-core:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-netty:1.57.2=unified-test-platform-core
io.grpc:grpc-netty:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-protobuf-lite:1.57.2=unified-test-platform-core
io.grpc:grpc-protobuf-lite:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-protobuf:1.57.2=unified-test-platform-core
io.grpc:grpc-protobuf:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-services:1.57.2=unified-test-platform-core
io.grpc:grpc-stub:1.57.2=unified-test-platform-core
io.grpc:grpc-stub:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.grpc:grpc-util:1.69.1=unified-test-platform-android-test-plugin-host-emulator-control
io.netty:netty-buffer:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-codec-http2:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-codec-http:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-codec-socks:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-codec:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-common:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-handler-proxy:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-handler:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-resolver:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-transport-native-unix-common:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.netty:netty-transport:4.1.133.Final=unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-core
io.opencensus:opencensus-api:0.31.0=unified-test-platform-core
io.opencensus:opencensus-proto:0.2.0=unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
io.perfmark:perfmark-api:0.26.0=unified-test-platform-core
io.perfmark:perfmark-api:0.27.0=unified-test-platform-android-test-plugin-host-emulator-control
jakarta.activation:jakarta.activation-api:1.2.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
javax.annotation:javax.annotation-api:1.3.2=unified-test-platform-android-test-plugin-host-emulator-control
javax.inject:javax.inject:1=androidLintTool,debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action
junit:junit:4.13.2=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
net.java.dev.jna:jna-platform:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
net.java.dev.jna:jna:5.6.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
net.sf.kxml:kxml2:2.3.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
net.zetetic:sqlcipher-android:4.10.0=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.apache.commons:commons-compress:1.27.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.apache.commons:commons-lang3:3.16.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.apache.cordova:framework:10.1.1=debugAndroidTestLintChecksClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.apache.httpcomponents:httpcore:4.4.16=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.apache.httpcomponents:httpmime:4.5.6=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.bouncycastle:bcpkix-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.bouncycastle:bcprov-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.bouncycastle:bcutil-jdk18on:1.79=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.checkerframework:checker-qual:3.33.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
org.checkerframework:checker-qual:3.43.0=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.codehaus.groovy:groovy:3.0.22=androidLintTool
org.codehaus.mojo:animal-sniffer-annotations:1.23=unified-test-platform-core
org.codehaus.mojo:animal-sniffer-annotations:1.24=unified-test-platform-android-test-plugin-host-emulator-control
org.glassfish.jaxb:jaxb-runtime:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.glassfish.jaxb:txw2:2.3.2=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.hamcrest:hamcrest-core:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath
org.hamcrest:hamcrest-library:1.3=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath
org.jacoco:org.jacoco.agent:0.8.14=androidJacocoAnt
org.jacoco:org.jacoco.ant:0.8.14=androidJacocoAnt
org.jacoco:org.jacoco.core:0.8.14=androidJacocoAnt
org.jacoco:org.jacoco.report:0.8.14=androidJacocoAnt
org.jetbrains.kotlin:kotlin-reflect:1.8.21=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-reflect:2.2.10=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.jetbrains.kotlin:kotlin-stdlib-common:1.8.21=unified-test-platform-android-test-plugin,unified-test-platform-core
org.jetbrains.kotlin:kotlin-stdlib-common:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-stdlib-common:2.2.10=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-gradle-work-action
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.20=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.20=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-core,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.jetbrains.kotlin:kotlin-stdlib:1.8.21=unified-test-platform-android-test-plugin,unified-test-platform-core
org.jetbrains.kotlin:kotlin-stdlib:1.8.22=debugAndroidTestCompileClasspath,debugAndroidTestLintChecksClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.9.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-launcher
org.jetbrains.kotlin:kotlin-stdlib:2.2.10=androidLintTool,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-gradle-work-action
org.jetbrains.kotlinx:atomicfu-jvm:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:atomicfu:0.22.0=unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1=debugAndroidTestLintChecksClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.6.4=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.1=debugAndroidTestLintChecksClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.6.4=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.1=debugAndroidTestLintChecksClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1=debugAndroidTestLintChecksClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3=unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.jetbrains:annotations:13.0=debugAndroidTestCompileClasspath,debugAndroidTestRuntimeClasspath,debugCompileClasspath,debugLintChecksClasspath,debugRuntimeClasspath,debugUnitTestCompileClasspath,debugUnitTestLintChecksClasspath,debugUnitTestRuntimeClasspath,releaseCompileClasspath,releaseLintChecksClasspath,releaseRuntimeClasspath
org.jetbrains:annotations:23.0.0=androidLintTool,debugAndroidTestLintChecksClasspath,unified-test-platform-android-device-provider-ddmlib,unified-test-platform-android-driver-instrumentation,unified-test-platform-android-test-plugin,unified-test-platform-android-test-plugin-host-additional-test-output,unified-test-platform-android-test-plugin-host-apk-installer,unified-test-platform-android-test-plugin-host-coverage,unified-test-platform-android-test-plugin-host-device-info,unified-test-platform-android-test-plugin-host-emulator-control,unified-test-platform-android-test-plugin-host-logcat,unified-test-platform-android-test-plugin-result-listener-gradle,unified-test-platform-core,unified-test-platform-gradle-work-action,unified-test-platform-launcher
org.jvnet.staxex:stax-ex:1.8.1=androidLintTool,unified-test-platform-android-test-plugin-result-listener-gradle
org.ow2.asm:asm-analysis:9.9=androidLintTool
org.ow2.asm:asm-commons:9.9=androidJacocoAnt,androidLintTool
org.ow2.asm:asm-tree:9.9=androidJacocoAnt,androidLintTool
org.ow2.asm:asm:9.9=androidJacocoAnt,androidLintTool
empty=androidApis,androidJdkImage,androidTestUtil,coreLibraryDesugaring,debugAndroidTestAnnotationProcessorClasspath,debugAnnotationProcessorClasspath,debugReverseMetadataValues,debugUnitTestAnnotationProcessorClasspath,lintChecks,lintPublish,releaseAnnotationProcessorClasspath,releaseReverseMetadataValues
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -0,0 +1,26 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import android.content.Context;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("com.getcapacitor.app", appContext.getPackageName());
}
}
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
android:name=".MainActivity"
android:label="@string/title_activity_main"
android:theme="@style/AppTheme.NoActionBarLaunch"
android:launchMode="singleTask"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -0,0 +1,5 @@
package com.kazerlelutin.bento;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

@@ -0,0 +1,34 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeColor="#00000000"
android:strokeWidth="1">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeColor="#00000000"
android:strokeWidth="1" />
</vector>
@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportHeight="108"
android:viewportWidth="108">
<path
android:fillColor="#26A69A"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF"
android:strokeWidth="0.8" />
</vector>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<WebView
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<resources>
<string name="app_name">Ben-to</string>
<string name="title_activity_main">Ben-to</string>
<string name="package_name">com.kazerlelutin.bento</string>
<string name="custom_url_scheme">com.kazerlelutin.bento</string>
</resources>
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
<item name="android:background">@null</item>
</style>
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
<item name="android:background">@drawable/splash</item>
</style>
</resources>
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>
@@ -0,0 +1,18 @@
package com.getcapacitor.myapp;
import static org.junit.Assert.*;
import org.junit.Test;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
+41
View File
@@ -0,0 +1,41 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
configurations.classpath {
resolutionStrategy.activateDependencyLocking()
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:9.2.0'
classpath 'com.google.gms:google-services:4.4.2'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
apply from: "variables.gradle"
allprojects {
repositories {
google()
mavenCentral()
}
}
// Verrouillage des dépendances sur :app uniquement (module versionné).
// Le dossier capacitor-cordova-android-plugins est ignoré par git (généré par Capacitor) : pas de lockfiles versionnés là-bas.
subprojects { sub ->
if (sub.name == 'app') {
sub.dependencyLocking {
lockAllConfigurations()
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
@@ -0,0 +1,118 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
androidx.databinding:databinding-common:9.2.0=classpath
androidx.databinding:databinding-compiler-common:9.2.0=classpath
com.android.databinding:baseLibrary:9.2.0=classpath
com.android.tools.analytics-library:crash:32.2.0=classpath
com.android.tools.analytics-library:protos:32.2.0=classpath
com.android.tools.analytics-library:shared:32.2.0=classpath
com.android.tools.analytics-library:tracker:32.2.0=classpath
com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10=classpath
com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10=classpath
com.android.tools.build:aapt2-proto:9.2.0-15009934=classpath
com.android.tools.build:aaptcompiler:9.2.0=classpath
com.android.tools.build:apksig:9.2.0=classpath
com.android.tools.build:apkzlib:9.2.0=classpath
com.android.tools.build:builder-model:9.2.0=classpath
com.android.tools.build:builder-test-api:9.2.0=classpath
com.android.tools.build:builder:9.2.0=classpath
com.android.tools.build:bundletool:1.18.3=classpath
com.android.tools.build:gradle-api:9.2.0=classpath
com.android.tools.build:gradle-common-api:9.2.0=classpath
com.android.tools.build:gradle-settings-api:9.2.0=classpath
com.android.tools.build:gradle:9.2.0=classpath
com.android.tools.build:manifest-merger:32.2.0=classpath
com.android.tools.ddms:ddmlib:32.2.0=classpath
com.android.tools.layoutlib:layoutlib-api:32.2.0=classpath
com.android.tools.lint:lint-model:32.2.0=classpath
com.android.tools.lint:lint-typedef-remover:32.2.0=classpath
com.android.tools.utp:gradle-work-action-api:32.2.0=classpath
com.android.tools:annotations:32.2.0=classpath
com.android.tools:common:32.2.0=classpath
com.android.tools:dvlib:32.2.0=classpath
com.android.tools:repository:32.2.0=classpath
com.android.tools:sdk-common:32.2.0=classpath
com.android.tools:sdklib:32.2.0=classpath
com.android:signflinger:9.2.0=classpath
com.android:zipflinger:9.2.0=classpath
com.google.android.gms:strict-version-matcher-plugin:1.2.4=classpath
com.google.auto.value:auto-value-annotations:1.6.2=classpath
com.google.code.findbugs:jsr305:3.0.2=classpath
com.google.code.gson:gson:2.11.0=classpath
com.google.crypto.tink:tink:1.7.0=classpath
com.google.dagger:dagger:2.28.3=classpath
com.google.errorprone:error_prone_annotations:2.28.0=classpath
com.google.flatbuffers:flatbuffers-java:1.12.0=classpath
com.google.gms:google-services:4.4.2=classpath
com.google.guava:failureaccess:1.0.2=classpath
com.google.guava:guava:33.3.1-jre=classpath
com.google.guava:listenablefuture:9999.0-empty-to-avoid-conflict-with-guava=classpath
com.google.j2objc:j2objc-annotations:3.0.0=classpath
com.google.jimfs:jimfs:1.1=classpath
com.google.protobuf:protobuf-java-util:3.25.5=classpath
com.google.protobuf:protobuf-java:3.25.5=classpath
com.googlecode.juniversalchardet:juniversalchardet:1.0.3=classpath
com.squareup:javapoet:1.13.0=classpath
com.squareup:javawriter:2.5.0=classpath
com.sun.istack:istack-commons-runtime:3.0.8=classpath
com.sun.xml.fastinfoset:FastInfoset:1.2.16=classpath
commons-codec:commons-codec:1.17.1=classpath
commons-io:commons-io:2.16.1=classpath
commons-logging:commons-logging:1.2=classpath
jakarta.activation:jakarta.activation-api:1.2.1=classpath
jakarta.xml.bind:jakarta.xml.bind-api:2.3.2=classpath
javax.inject:javax.inject:1=classpath
net.java.dev.jna:jna-platform:5.6.0=classpath
net.java.dev.jna:jna:5.6.0=classpath
net.sf.jopt-simple:jopt-simple:4.9=classpath
net.sf.kxml:kxml2:2.3.0=classpath
org.apache.commons:commons-compress:1.27.1=classpath
org.apache.commons:commons-lang3:3.16.0=classpath
org.apache.httpcomponents:httpclient:4.5.14=classpath
org.apache.httpcomponents:httpcore:4.4.16=classpath
org.apache.httpcomponents:httpmime:4.5.6=classpath
org.bitbucket.b_c:jose4j:0.9.5=classpath
org.bouncycastle:bcpkix-jdk18on:1.79=classpath
org.bouncycastle:bcprov-jdk18on:1.79=classpath
org.bouncycastle:bcutil-jdk18on:1.79=classpath
org.checkerframework:checker-qual:3.43.0=classpath
org.glassfish.jaxb:jaxb-runtime:2.3.2=classpath
org.glassfish.jaxb:txw2:2.3.2=classpath
org.jdom:jdom2:2.0.6=classpath
org.jetbrains.kotlin:abi-tools-api:2.2.10=classpath
org.jetbrains.kotlin:fus-statistics-gradle-plugin:2.2.10=classpath
org.jetbrains.kotlin:kotlin-build-statistics:2.2.10=classpath
org.jetbrains.kotlin:kotlin-build-tools-api:2.2.10=classpath
org.jetbrains.kotlin:kotlin-compiler-runner:2.2.10=classpath
org.jetbrains.kotlin:kotlin-daemon-client:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-annotations:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-api:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea-proto:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-idea:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-model:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.10=classpath
org.jetbrains.kotlin:kotlin-gradle-plugins-bom:2.2.10=classpath
org.jetbrains.kotlin:kotlin-klib-commonizer-api:2.2.10=classpath
org.jetbrains.kotlin:kotlin-native-utils:2.2.10=classpath
org.jetbrains.kotlin:kotlin-reflect:2.2.10=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.2.10=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.2.10=classpath
org.jetbrains.kotlin:kotlin-stdlib:2.2.10=classpath
org.jetbrains.kotlin:kotlin-tooling-core:2.2.10=classpath
org.jetbrains.kotlin:kotlin-util-io:2.2.10=classpath
org.jetbrains.kotlin:kotlin-util-klib-metadata:2.2.10=classpath
org.jetbrains.kotlin:kotlin-util-klib:2.2.10=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.9.0=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.9.0=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0=classpath
org.jetbrains:annotations:23.0.0=classpath
org.jvnet.staxex:stax-ex:1.8.1=classpath
org.ow2.asm:asm-analysis:9.9=classpath
org.ow2.asm:asm-commons:9.9=classpath
org.ow2.asm:asm-tree:9.9=classpath
org.ow2.asm:asm-util:9.9=classpath
org.ow2.asm:asm:9.9=classpath
org.slf4j:slf4j-api:1.7.30=classpath
org.tensorflow:tensorflow-lite-metadata:0.2.0=classpath
empty=
@@ -0,0 +1,9 @@
// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN
include ':capacitor-android'
project(':capacitor-android').projectDir = new File('../../../node_modules/@capacitor/android/capacitor')
include ':capacitor-share'
project(':capacitor-share').projectDir = new File('../../../node_modules/@capacitor/share/android')
include ':capacitor-community-sqlite'
project(':capacitor-community-sqlite').projectDir = new File('../../../node_modules/@capacitor-community/sqlite/android')
+32
View File
@@ -0,0 +1,32 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx1536m
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
android.defaults.buildfeatures.resvalues=true
android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
android.enableAppCompileTimeRClass=false
android.usesSdkInManifest.disallowed=false
android.uniquePackageNames=false
android.dependency.useConstraints=true
android.r8.strictFullModeForKeepRules=false
android.r8.optimizedResourceShrinking=false
android.builtInKotlin=false
android.newDsl=false
Binary file not shown.
@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
Vendored Executable
+252
View File
@@ -0,0 +1,252 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
+94
View File
@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@@ -0,0 +1,7 @@
# Copier vers keystore.properties (non versionné) à côté de ce fichier.
# Génération du magasin : keytool -genkey -v -keystore release.keystore -alias BentoRelease -keyalg RSA -keysize 2048 -validity 10000
#
storeFile=release.keystore
storePassword=
keyAlias=BentoRelease
keyPassword=
+5
View File
@@ -0,0 +1,5 @@
include ':app'
include ':capacitor-cordova-android-plugins'
project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/')
apply from: 'capacitor.settings.gradle'
+16
View File
@@ -0,0 +1,16 @@
ext {
minSdkVersion = 23
compileSdkVersion = 35
targetSdkVersion = 35
androidxActivityVersion = '1.9.2'
androidxAppCompatVersion = '1.7.0'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.15.0'
androidxFragmentVersion = '1.8.4'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.12.1'
junitVersion = '4.13.2'
androidxJunitVersion = '1.2.1'
androidxEspressoCoreVersion = '3.6.1'
cordovaAndroidVersion = '10.1.1'
}
+12
View File
@@ -0,0 +1,12 @@
import type { CapacitorConfig } from "@capacitor/cli";
const config: CapacitorConfig = {
appId: "com.kazerlelutin.bento",
appName: "Bento",
webDir: "../web/dist",
server: {
androidScheme: "https"
}
};
export default config;
+21
View File
@@ -0,0 +1,21 @@
{
"name": "@ben-to/mobile",
"version": "0.1.0",
"private": true,
"scripts": {
"sync": "npx cap sync",
"open:android": "npx cap open android",
"open:ios": "npx cap open ios",
"run:android": "npx cap run android"
},
"dependencies": {
"@capacitor/android": "^8.4.1",
"@capacitor/core": "^8.4.1",
"@capacitor/ios": "^8.4.1",
"@capacitor/share": "^8.0.1",
"@capacitor-community/sqlite": "^8.1.0"
},
"devDependencies": {
"@capacitor/cli": "^8.4.1"
}
}
+4
View File
@@ -0,0 +1,4 @@
# Optionnel — Matomo (mesure daudience). Les deux variables sont requises pour activer
# le tracker et le bandeau de consentement cookies.
# VITE_MATOMO_URL=https://analytics.example.com/
# VITE_MATOMO_SITE_ID=1
+39
View File
@@ -0,0 +1,39 @@
<!doctype html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description"
content="Composez vos bento idéaux grâce au système forme, goût et recette. Des idées de repas savoureux et faciles à transporter, illustrées en pixel-art." />
<meta name="theme-color" content="#f1e9de" />
<link rel="canonical" href="https://ben-to.fr/" />
<meta property="og:site_name" content="Ben-to" />
<meta property="og:url" content="https://ben-to.fr/" />
<meta property="og:title" content="Bento — Ben-to" />
<meta property="og:description"
content="Composez vos bento idéaux grâce au système forme, goût et recette. Des idées de repas savoureux et faciles à transporter, illustrées en pixel-art." />
<meta property="og:type" content="website" />
<meta property="og:locale" content="fr_FR" />
<meta property="og:locale:alternate" content="en_US" />
<meta property="og:locale:alternate" content="ko_KR" />
<meta property="og:locale:alternate" content="zh_CN" />
<meta property="og:image" content="https://ben-to.fr/branding/og.jpg" />
<meta property="og:image:alt" content="Logo onigiri pixel-art — Ben-to" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="Bento — Ben-to" />
<meta name="twitter:description"
content="Composez vos bento idéaux grâce au système forme, goût et recette. Des idées de repas savoureux et faciles à transporter, illustrées en pixel-art." />
<meta name="twitter:image" content="https://ben-to.fr/branding/og.jpg" />
<meta name="twitter:image:alt" content="Logo onigiri pixel-art — Ben-to" />
<link rel="icon" type="image/png" href="/branding/onigiri-logo.png" />
<link rel="apple-touch-icon" href="/branding/onigiri-logo.png" />
<title>Bento</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@ben-to/web",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"predev": "node ../../scripts/build-recipes.mjs",
"dev": "vite",
"prebuild": "node ../../scripts/build-recipes.mjs",
"build": "vite build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.tsx",
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
"@ben-to/builder": "0.1.0",
"@ben-to/i18n": "0.1.0",
"@ben-to/privacy": "0.1.0",
"@ben-to/recipes": "0.1.0",
"@capacitor/core": "^8.4.1",
"@capacitor/share": "^8.0.1",
"@solidjs/router": "^0.16.1",
"solid-js": "^1.9.14"
},
"devDependencies": {
"typescript": "^6.0.3",
"vite": "^8.1.4",
"vite-plugin-solid": "^2.11.8"
}
}
+93
View File
@@ -0,0 +1,93 @@
Copyright 2025 The Cause Project Authors (https://github.com/xconsau/Cause)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
@@ -0,0 +1,93 @@
Copyright (c) YoonDesign Inc. All Rights Reserved.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Binary file not shown.
Binary file not shown.
+93
View File
@@ -0,0 +1,93 @@
Copyright 2020 The Klee Project Authors (https://github.com/fontworks-fonts/Klee)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+22
View File
@@ -0,0 +1,22 @@
,assiette,fouet,baguettes,carnet,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
eau|vinaigre de riz|Vinaigre blanc|mirin,huile|jus de citron,jus d'orange,jus de citron vert,lait|crême fraîche liquide entière|Crème fraîche épaisse,chocolat fondu|sauce soja|aromate pistache,purée de sésame noir,moutarde forte,Chapelure panko|Chapelure,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
onigiri,empenadas,gimbap,radis mariné,kimchi,ramen,tofu,udon,dan bing|Omelette roulée,mandu,muffin,madelaine|madeleine,naan,bao,mandu shiitake,mandu aubergine,onigiri kimchi,blinis,cheese naan fromage,empanada-intensesalty,empanadas-butternut,empanadas-citron,madeleine-chocolat-blanc,guacamole
gateau de savoie,tranche cake,banana cake,carrot cake,cake au citron,cake à l'orange,flan,cheesecake-speculoos,speculoos,financier,financier lin,muffin au sésame noir,muffin aux pépites de chocolat,muffin chocolat blanc,muffin pistache et chocolat blanc,muffin pomme cannelle,muffin menthe et pépites de chocolat,gua bao,,,,,,
citron,orange,pomme rouge|Pomme,pomme verte,poire,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
betterave,carotte| carottes râpées,feuille de nori,cornichon|concombre,poivron rouge|poivron,poivron orange,poivron vert,poivron jaune,gingembre,navet,danmuji|radis jaune,aubergine|aubergines,champignon|champignons|shiitake,ail|aïl|ail frais|,radis|radis râpé,tomate|tomates,melon|melons,,,,,,,
haricots noirs,flageolets,haricots rouges,mais,epinard,courge,oignon,piment rouge,ciboulette,ciboule,piment vert,banane|bananes,Avocat|avocats|Avocados,Butternut|butternuts,Feuilles de coriandre|coriandre,Échalote|echalote,menthe|mint,,,,,,,
oeuf|oeufs|œufs|œuf,jaune d'oeuf|jaune d'œufs|jaune d'œuf,blanc d'oeuf|blanc d'œufs|blanc d'œuf,,,,,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
sel|bicarbonate|fleur de sel,poivre,poudre verte,poudre beige|poudre d'amande|poudre d'amandes|canelle en poudre|cannelle|cassonade|sucre brun|Levure boulangère sèche,gingembre en poudre,poudre blanche|sucre en poudre|sucre|fécule|fécule de maïs|levure chimique,poudre rouge|piment de cayenne,poudre orange,,,,,,,,,,,,,,,,
vanille,noisette|noisettes,noix,Muscade|muscade,Clous de girofle |clous de girofle|girofle,,,,,,,,,,,,,,,,,,,
pepite de chocolat au lait,pepites de chocolat noir,pepite de chocolat blanc|pepite chocolat blanc,,,,,,,,,,,,,,,,,,,,,
tablette chocolat|chocolat,tablette chocolat blanc|chocolat blanc,,,,,,,,,,,,,,,,,,,,,,
emmental,beurre|beurre doux,mozzarella,comté|gruyère,cheddar,fromage frais|yaourt grec,fromage râpé,,,,,,,,,,,,,,,,,
,,,,,,,,,,,,,,,,,,,,,,,
farine,sucre de canne,,,,,,,,,,,,,,,,,,,,,,
riz,,,,,,,,,,,,,,,,,,,,,,,
graines de sésame|graines de lin,,,,,,,,,,,,,,,,,,,,,,,
arachide|---,,,,,,,,,,,,,,,,,,,,,,,
1 assiette fouet baguettes carnet
2
3 eau|vinaigre de riz|Vinaigre blanc|mirin huile|jus de citron jus d'orange jus de citron vert lait|crême fraîche liquide entière|Crème fraîche épaisse chocolat fondu|sauce soja|aromate pistache purée de sésame noir moutarde forte Chapelure panko|Chapelure
4
5 onigiri empenadas gimbap radis mariné kimchi ramen tofu udon dan bing|Omelette roulée mandu muffin madelaine|madeleine naan bao mandu shiitake mandu aubergine onigiri kimchi blinis cheese naan fromage empanada-intensesalty empanadas-butternut empanadas-citron madeleine-chocolat-blanc guacamole
6 gateau de savoie tranche cake banana cake carrot cake cake au citron cake à l'orange flan cheesecake-speculoos speculoos financier financier lin muffin au sésame noir muffin aux pépites de chocolat muffin chocolat blanc muffin pistache et chocolat blanc muffin pomme cannelle muffin menthe et pépites de chocolat gua bao
7 citron orange pomme rouge|Pomme pomme verte poire
8
9 betterave carotte| carottes râpées feuille de nori cornichon|concombre poivron rouge|poivron poivron orange poivron vert poivron jaune gingembre navet danmuji|radis jaune aubergine|aubergines champignon|champignons|shiitake ail|aïl|ail frais| radis|radis râpé tomate|tomates melon|melons
10 haricots noirs flageolets haricots rouges mais epinard courge oignon piment rouge ciboulette ciboule piment vert banane|bananes Avocat|avocats|Avocados Butternut|butternuts Feuilles de coriandre|coriandre Échalote|echalote menthe|mint
11 oeuf|oeufs|œufs|œuf jaune d'oeuf|jaune d'œufs|jaune d'œuf blanc d'oeuf|blanc d'œufs|blanc d'œuf
12
13 sel|bicarbonate|fleur de sel poivre poudre verte poudre beige|poudre d'amande|poudre d'amandes|canelle en poudre|cannelle|cassonade|sucre brun|Levure boulangère sèche gingembre en poudre poudre blanche|sucre en poudre|sucre|fécule|fécule de maïs|levure chimique poudre rouge|piment de cayenne poudre orange
14 vanille noisette|noisettes noix Muscade|muscade Clous de girofle |clous de girofle|girofle
15 pepite de chocolat au lait pepites de chocolat noir pepite de chocolat blanc|pepite chocolat blanc
16 tablette chocolat|chocolat tablette chocolat blanc|chocolat blanc
17 emmental beurre|beurre doux mozzarella comté|gruyère cheddar fromage frais|yaourt grec fromage râpé
18
19 farine sucre de canne
20 riz
21 graines de sésame|graines de lin
22 arachide|---
Binary file not shown.

After

Width:  |  Height:  |  Size: 378 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

+4
View File
@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://ben-to.fr/sitemap.xml
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://ben-to.fr/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://ben-to.fr/a-propos</loc>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
<url>
<loc>https://ben-to.fr/cgu</loc>
<changefreq>yearly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://ben-to.fr/mentions-legales</loc>
<changefreq>yearly</changefreq>
<priority>0.5</priority>
</url>
<url>
<loc>https://ben-to.fr/confidentialite</loc>
<changefreq>yearly</changefreq>
<priority>0.5</priority>
</url>
</urlset>
Binary file not shown.

After

Width:  |  Height:  |  Size: 298 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

+13
View File
@@ -0,0 +1,13 @@
import { readAnalyticsConsent } from "./cookie-consent-storage";
type MatomoWindow = Window &
typeof globalThis & {
_paq?: unknown[][];
};
export const trackEvent = (category: string, action: string, name?: string): void => {
if (readAnalyticsConsent() !== "analytics-accepted") return;
const matomoWindow = window as MatomoWindow;
matomoWindow._paq = matomoWindow._paq ?? [];
matomoWindow._paq.push(["trackEvent", category, action, name ?? ""]);
};
+34
View File
@@ -0,0 +1,34 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("./cookie-consent-storage", () => ({
readAnalyticsConsent: vi.fn()
}));
import { readAnalyticsConsent } from "./cookie-consent-storage";
import { trackEvent } from "./analytics";
const readConsent = vi.mocked(readAnalyticsConsent);
describe("trackEvent (Matomo)", () => {
beforeEach(() => {
readConsent.mockReset();
readConsent.mockReturnValue("analytics-rejected");
vi.stubGlobal("window", { _paq: [] as unknown[][] });
});
afterEach(() => {
vi.unstubAllGlobals();
});
it("nenvoie rien sans consentement analytics", () => {
trackEvent("recipe", "share", "x");
expect(window._paq?.length ?? 0).toBe(0);
});
it("pousse un trackEvent Matomo lorsque le consentement est accepté", () => {
readConsent.mockReturnValue("analytics-accepted");
vi.stubGlobal("window", { _paq: [] as unknown[][] });
trackEvent("recipe", "share", "y");
expect(window._paq).toContainEqual(["trackEvent", "recipe", "share", "y"]);
});
});
@@ -0,0 +1,22 @@
import {
COOKIE_CONSENT_STORAGE_KEY,
normalizeConsentRaw,
type ConsentParseResult,
type StoredAnalyticsConsent
} from "@ben-to/privacy/cookie-consent";
export const readAnalyticsConsent = (): ConsentParseResult => {
try {
return normalizeConsentRaw(localStorage.getItem(COOKIE_CONSENT_STORAGE_KEY));
} catch {
return "unknown";
}
};
export const writeAnalyticsConsent = (value: StoredAnalyticsConsent): void => {
try {
localStorage.setItem(COOKIE_CONSENT_STORAGE_KEY, value);
} catch {
/* stockage indisponible (mode privé strict, etc.) */
}
};
+60
View File
@@ -0,0 +1,60 @@
import { readAnalyticsConsent } from "./cookie-consent-storage";
export type MatomoRuntimeConfig = Readonly<{
baseUrl: string;
siteId: string;
}>;
type MatomoWindow = Window &
typeof globalThis & {
_paq?: unknown[][];
};
let injectPromise: Promise<void> | null = null;
export const getMatomoRuntimeConfig = (): MatomoRuntimeConfig | null => {
const rawUrl = import.meta.env.VITE_MATOMO_URL;
const rawSite = import.meta.env.VITE_MATOMO_SITE_ID;
const url = typeof rawUrl === "string" ? rawUrl.trim() : "";
const siteId = typeof rawSite === "string" ? rawSite.trim() : "";
if (!url || !siteId) return null;
const baseUrl = url.endsWith("/") ? url : `${url}/`;
return { baseUrl, siteId };
};
export const isMatomoConfigured = (): boolean => getMatomoRuntimeConfig() !== null;
export const injectMatomoTracker = (cfg: MatomoRuntimeConfig): Promise<void> => {
if (injectPromise) return injectPromise;
injectPromise = new Promise<void>((resolve, reject) => {
const w = window as MatomoWindow;
w._paq = w._paq ?? [];
w._paq.push(["setTrackerUrl", `${cfg.baseUrl}matomo.php`]);
w._paq.push(["setSiteId", cfg.siteId]);
const g = document.createElement("script");
g.async = true;
g.src = `${cfg.baseUrl}matomo.js`;
g.onload = () => {
const mw = window as MatomoWindow;
mw._paq?.push(["enableLinkTracking"]);
resolve();
};
g.onerror = () => reject(new Error("Failed to load matomo.js"));
const s = document.getElementsByTagName("script")[0];
if (s?.parentNode) {
s.parentNode.insertBefore(g, s);
} else {
document.head.appendChild(g);
}
});
return injectPromise;
};
export const trackMatomoPageView = (): void => {
if (readAnalyticsConsent() !== "analytics-accepted") return;
const w = window as MatomoWindow;
if (!w._paq) return;
w._paq.push(["setCustomUrl", window.location.href]);
w._paq.push(["setDocumentTitle", document.title]);
w._paq.push(["trackPageView"]);
};
+12
View File
@@ -0,0 +1,12 @@
import type { BentoSelection } from "@ben-to/builder/domain";
const key = "ben-to.selection";
export const saveSelection = (selection: BentoSelection): void => {
localStorage.setItem(key, JSON.stringify(selection));
};
export const loadSelection = (): BentoSelection | null => {
const raw = localStorage.getItem(key);
return raw ? (JSON.parse(raw) as BentoSelection) : null;
};
@@ -0,0 +1,54 @@
const STORAGE_KEY = "ben-to.recipe-checklist";
export type RecipeChecklistStored = Readonly<{
ingredientIds: readonly string[];
stepIds: readonly string[];
}>;
type RecipeChecklistMap = Record<string, RecipeChecklistStored>;
const emptyEntry = (): RecipeChecklistStored =>
Object.freeze({ ingredientIds: Object.freeze([]), stepIds: Object.freeze([]) });
const loadMap = (): RecipeChecklistMap => {
if (typeof localStorage === "undefined") return {};
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return {};
const parsed = JSON.parse(raw) as unknown;
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return {};
return parsed as RecipeChecklistMap;
} catch {
return {};
}
};
const saveMap = (map: RecipeChecklistMap): void => {
if (typeof localStorage === "undefined") return;
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(map));
} catch {
/* quota / private mode */
}
};
export const loadRecipeChecklist = (recipeKey: string): RecipeChecklistStored => {
const map = loadMap();
const entry = map[recipeKey];
if (!entry || !Array.isArray(entry.ingredientIds) || !Array.isArray(entry.stepIds)) {
return emptyEntry();
}
return Object.freeze({
ingredientIds: Object.freeze([...entry.ingredientIds]),
stepIds: Object.freeze([...entry.stepIds])
});
};
export const saveRecipeChecklist = (recipeKey: string, data: RecipeChecklistStored): void => {
const map = loadMap();
map[recipeKey] = Object.freeze({
ingredientIds: Object.freeze([...data.ingredientIds]),
stepIds: Object.freeze([...data.stepIds])
});
saveMap(map);
};
@@ -0,0 +1,36 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
readWelcomeTourDismissed,
WELCOME_TOUR_SCHEMA_VERSION,
WELCOME_TOUR_STORAGE_KEY,
writeWelcomeTourDismissed
} from "./welcome-tour-storage";
const mockStorage = () => {
const map = new Map<string, string>();
vi.stubGlobal("localStorage", {
getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
setItem: (k: string, v: string) => void map.set(k, v),
removeItem: (k: string) => void map.delete(k),
clear: () => void map.clear(),
key: (i: number) => [...map.keys()][i] ?? null,
get length() {
return map.size;
}
} as Storage);
};
describe("welcome tour storage (Gherkin @int-welcome-tour-dismiss)", () => {
afterEach(() => {
vi.unstubAllGlobals();
});
/** @int-welcome-tour-dismiss */
it("enregistre la version courante lorsque le tour est fermé", () => {
mockStorage();
expect(readWelcomeTourDismissed()).toBe(false);
writeWelcomeTourDismissed();
expect(localStorage.getItem(WELCOME_TOUR_STORAGE_KEY)).toBe(WELCOME_TOUR_SCHEMA_VERSION);
expect(readWelcomeTourDismissed()).toBe(true);
});
});
@@ -0,0 +1,21 @@
/** Clé localStorage : fermeture du tour d'accueil pour une version de contenu donnée. */
export const WELCOME_TOUR_STORAGE_KEY = "ben-to.welcome-tour-dismissed";
/** Incrémenter pour réafficher le tour après refonte majeure du contenu. */
export const WELCOME_TOUR_SCHEMA_VERSION = "v1";
export const readWelcomeTourDismissed = (): boolean => {
try {
return localStorage.getItem(WELCOME_TOUR_STORAGE_KEY) === WELCOME_TOUR_SCHEMA_VERSION;
} catch {
return false;
}
};
export const writeWelcomeTourDismissed = (): void => {
try {
localStorage.setItem(WELCOME_TOUR_STORAGE_KEY, WELCOME_TOUR_SCHEMA_VERSION);
} catch {
/* stockage indisponible */
}
};
@@ -0,0 +1,45 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import {
readWelcomeTourDismissed,
WELCOME_TOUR_SCHEMA_VERSION,
WELCOME_TOUR_STORAGE_KEY,
writeWelcomeTourDismissed
} from "./welcome-tour-storage";
const mockStorage = () => {
const map = new Map<string, string>();
vi.stubGlobal("localStorage", {
getItem: (k: string) => (map.has(k) ? map.get(k)! : null),
setItem: (k: string, v: string) => void map.set(k, v),
removeItem: (k: string) => void map.delete(k),
clear: () => void map.clear(),
key: (i: number) => [...map.keys()][i] ?? null,
get length() {
return map.size;
}
} as Storage);
};
describe("welcome-tour-storage", () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it("readWelcomeTourDismissed est faux sans valeur", () => {
mockStorage();
expect(readWelcomeTourDismissed()).toBe(false);
});
it("write puis read indique dismiss pour la version courante", () => {
mockStorage();
writeWelcomeTourDismissed();
expect(localStorage.getItem(WELCOME_TOUR_STORAGE_KEY)).toBe(WELCOME_TOUR_SCHEMA_VERSION);
expect(readWelcomeTourDismissed()).toBe(true);
});
it("une ancienne valeur ne compte pas comme dismiss", () => {
mockStorage();
localStorage.setItem(WELCOME_TOUR_STORAGE_KEY, "v0");
expect(readWelcomeTourDismissed()).toBe(false);
});
});
+5
View File
@@ -0,0 +1,5 @@
import { render } from "solid-js/web";
import { App } from "./ui/App";
import "./ui/app.css";
render(() => <App />, document.getElementById("root")!);
+18
View File
@@ -0,0 +1,18 @@
import { Route, Router } from "@solidjs/router";
import { BuilderFlow } from "./BuilderFlow";
import { RootLayout } from "./RootLayout";
import { StaticInfoPage } from "./pages/StaticInfoPage";
export function App() {
return (
<Router root={RootLayout}>
<Route path="/" component={BuilderFlow} />
<Route path="/r/:baseId/:variantId" component={BuilderFlow} />
<Route path="/a-propos" component={() => <StaticInfoPage kind="about" />} />
<Route path="/cgu" component={() => <StaticInfoPage kind="terms" />} />
<Route path="/mentions-legales" component={() => <StaticInfoPage kind="legal" />} />
<Route path="/confidentialite" component={() => <StaticInfoPage kind="privacy" />} />
<Route path="*" component={() => <StaticInfoPage kind="notFound" />} />
</Router>
);
}
+126
View File
@@ -0,0 +1,126 @@
import type { RouteSectionProps } from "@solidjs/router";
import type { BentoSelection } from "@ben-to/builder/domain";
import { createInitialSelection } from "@ben-to/builder/domain";
import { fetchRecipesIndex } from "@ben-to/recipes/catalog";
import { getDictionary } from "@ben-to/i18n/dictionary";
import { createEffect, createMemo, createResource, createSignal, Show } from "solid-js";
import { clearBuilderResetBus, readBuilderResetNonce } from "./builder-reset-bus";
import { useAppLocale } from "./app-locale-context";
import { BuilderBaseStep } from "./builder-flow/BuilderBaseStep";
import { BuilderCtaDock } from "./builder-flow/BuilderCtaDock";
import { BuilderStepsNav } from "./builder-flow/BuilderStepsNav";
import { BuilderVariantStep } from "./builder-flow/BuilderVariantStep";
import { RecipeSheetSection } from "./builder-flow/RecipeSheetSection";
import { useBuilderCatalog } from "./builder-flow/use-builder-catalog";
import { useBuilderSteps } from "./builder-flow/use-builder-steps";
import { useRecipeSheet } from "./builder-flow/use-recipe-sheet";
export function BuilderFlow(_props: RouteSectionProps) {
const { locale } = useAppLocale();
const [selection, setSelection] = createSignal<BentoSelection>(createInitialSelection());
const [recipesIndex] = createResource(async () => fetchRecipesIndex());
const t = createMemo(() => getDictionary(locale()));
const steps = useBuilderSteps({
selection,
setSelection,
recipesIndex
});
const catalog = useBuilderCatalog({
recipesIndex,
selection,
flavorFilter: steps.flavorFilter,
locale
});
const recipe = useRecipeSheet({
selection,
currentStep: steps.currentStep,
mergedRecipe: catalog.mergedRecipe,
recipeDisplayTitle: catalog.recipeDisplayTitle,
locale,
t
});
createEffect(() => {
const n = readBuilderResetNonce();
if (n === 0) return;
steps.goHomeWizard();
recipe.resetServingsForGoHome();
clearBuilderResetBus();
});
return (
<main
classList={{
"app-shell": true,
"app-shell--with-dock": steps.canContinue(),
"app-shell--internal-scroll": true,
"app-shell--recipe-scroll": steps.currentStep() === "recipe"
}}
>
<BuilderStepsNav
t={t}
progressStepIndex={steps.progressStepIndex}
progressPercent={steps.progressPercent}
canNavigateToStep={steps.canNavigateToStep}
goToBuilderStep={steps.goToBuilderStep}
currentStep={steps.currentStep}
recipeProgressCompact={recipe.recipeProgressCompact}
/>
<Show when={steps.currentStep() === "base"}>
<div class="builder-scroll">
<BuilderBaseStep
t={t}
locale={locale}
recipesIndex={recipesIndex}
filteredBases={catalog.filteredBases}
flavorFilter={steps.flavorFilter}
setFlavorFilter={steps.setFlavorFilter}
selection={selection}
setSelection={setSelection}
advanceAfterBasePickDouble={steps.advanceAfterBasePickDouble}
onPickCardTouchEndForDouble={steps.onPickCardTouchEndForDouble}
/>
</div>
</Show>
<Show when={steps.currentStep() === "variant"}>
<div class="builder-scroll">
<BuilderVariantStep
t={t}
locale={locale}
currentBase={catalog.currentBase}
availableVariants={catalog.availableVariants}
selection={selection}
setSelection={setSelection}
advanceAfterVariantPickDouble={steps.advanceAfterVariantPickDouble}
onPickCardTouchEndForDouble={steps.onPickCardTouchEndForDouble}
/>
</div>
</Show>
<RecipeSheetSection
currentStep={steps.currentStep}
locale={locale}
t={t}
recipeCoverImageUrl={catalog.recipeCoverImageUrl}
recipeDisplayTitle={catalog.recipeDisplayTitle}
mergedRecipe={catalog.mergedRecipe}
recipe={recipe}
/>
<BuilderCtaDock
canContinue={steps.canContinue}
currentStep={steps.currentStep}
currentBase={catalog.currentBase}
currentVariant={catalog.currentVariant}
locale={locale}
t={t}
nextStep={steps.nextStep}
/>
</main>
);
}
+41
View File
@@ -0,0 +1,41 @@
import { A } from "@solidjs/router";
import type { Accessor } from "solid-js";
import type { Dictionary } from "@ben-to/i18n/dictionary";
export function CookieConsentBanner(props: {
readonly t: Accessor<Dictionary>;
readonly onAccept: () => void;
readonly onReject: () => void;
}) {
return (
<div class="cookie-consent" role="region" aria-labelledby="cookie-consent-heading">
<div class="cookie-consent__inner">
<h2 id="cookie-consent-heading" class="cookie-consent__title">
{props.t().cookieConsentTitle}
</h2>
<p class="cookie-consent__text">{props.t().cookieConsentDescription}</p>
<p class="cookie-consent__privacy">
<A href="/confidentialite" class="cookie-consent__link">
{props.t().navPrivacy}
</A>
</p>
<div class="cookie-consent__actions">
<button
type="button"
class="cookie-consent__btn cookie-consent__btn--primary"
onClick={() => props.onAccept()}
>
{props.t().cookieConsentAccept}
</button>
<button
type="button"
class="cookie-consent__btn cookie-consent__btn--ghost"
onClick={() => props.onReject()}
>
{props.t().cookieConsentReject}
</button>
</div>
</div>
</div>
);
}
+222
View File
@@ -0,0 +1,222 @@
import { A, useLocation } from "@solidjs/router";
import type { RouteSectionProps } from "@solidjs/router";
import { createEffect, createSignal, For, onCleanup, onMount, Show } from "solid-js";
import { getDictionary, type LocaleCode } from "@ben-to/i18n/dictionary";
import { readAnalyticsConsent, writeAnalyticsConsent } from "../infra/cookie-consent-storage";
import { readWelcomeTourDismissed, writeWelcomeTourDismissed } from "../infra/welcome-tour-storage";
import { getMatomoRuntimeConfig, injectMatomoTracker, trackMatomoPageView } from "../infra/matomo";
import { bumpBuilderResetBus } from "./builder-reset-bus";
import { AppLocaleProvider, useAppLocale } from "./app-locale-context";
import { CookieConsentBanner } from "./CookieConsentBanner";
import { WelcomeTourModal } from "./WelcomeTourModal";
import { publicAssetHref } from "./builder-flow/public-asset";
const localeOptions: LocaleCode[] = ["fr", "en", "ko", "zh"];
const localeNativeNames: Record<LocaleCode, string> = {
fr: "Français",
en: "English",
ko: "한국어",
zh: "中文"
};
const localeFlags: Record<LocaleCode, string> = {
fr: "🇫🇷",
en: "🇬🇧",
ko: "🇰🇷",
zh: "🇨🇳"
};
function RootLayoutInner(props: RouteSectionProps) {
const { locale, setLocale } = useAppLocale();
const location = useLocation();
const [menuOpen, setMenuOpen] = createSignal(false);
const t = () => getDictionary(locale());
const matomoCfg = getMatomoRuntimeConfig();
const [matomoReady, setMatomoReady] = createSignal(false);
const [consentBannerOpen, setConsentBannerOpen] = createSignal(readAnalyticsConsent() === "unknown");
const [welcomeTourOpen, setWelcomeTourOpen] = createSignal(false);
createEffect(() => {
location.pathname;
setMenuOpen(false);
});
createEffect(() => {
location.pathname;
location.search;
if (!matomoReady()) return;
trackMatomoPageView();
});
const acceptAnalyticsCookies = () => {
writeAnalyticsConsent("analytics-accepted");
setConsentBannerOpen(false);
if (matomoCfg) {
void injectMatomoTracker(matomoCfg).then(() => setMatomoReady(true));
}
};
const rejectAnalyticsCookies = () => {
writeAnalyticsConsent("analytics-rejected");
setConsentBannerOpen(false);
};
const dismissWelcomeTour = () => {
writeWelcomeTourDismissed();
setWelcomeTourOpen(false);
};
createEffect(() => {
location.pathname;
consentBannerOpen();
if (location.pathname !== "/" || consentBannerOpen()) {
setWelcomeTourOpen(false);
return;
}
if (readWelcomeTourDismissed()) {
setWelcomeTourOpen(false);
return;
}
setWelcomeTourOpen(true);
});
onMount(() => {
const consent = readAnalyticsConsent();
if (consent === "unknown") setConsentBannerOpen(true);
if (consent === "analytics-accepted" && matomoCfg) {
void injectMatomoTracker(matomoCfg).then(() => setMatomoReady(true));
}
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape") {
if (welcomeTourOpen()) {
dismissWelcomeTour();
return;
}
setMenuOpen(false);
}
};
window.addEventListener("keydown", onKeyDown);
onCleanup(() => window.removeEventListener("keydown", onKeyDown));
});
return (
<>
<header class="topbar">
<button
type="button"
class="topbar__burger"
aria-expanded={menuOpen()}
aria-controls="site-nav-drawer"
aria-label={t().menu}
onClick={() => setMenuOpen((open) => !open)}
>
<span class="topbar__burger-line" aria-hidden="true" />
<span class="topbar__burger-line" aria-hidden="true" />
<span class="topbar__burger-line" aria-hidden="true" />
</button>
<h1 class="topbar__title">
<A
href="/"
class="brand"
aria-label={t().home}
end
onClick={(event) => {
if (location.pathname === "/") {
event.preventDefault();
bumpBuilderResetBus();
}
}}
>
<img class="brand__logo" src={publicAssetHref("/branding/onigiri-logo.png")} width="40" height="40" alt="" />
<span class="brand__wordmark" aria-hidden="true">
<span class="brand__ben">Ben</span>
<span class="brand__to">to</span>
</span>
</A>
</h1>
<div class="topbar__locale" role="radiogroup" aria-label={t().language}>
<For each={localeOptions}>
{(code) => (
<button
type="button"
class="topbar__locale-btn"
classList={{ "topbar__locale-btn--active": locale() === code }}
role="radio"
aria-checked={locale() === code}
title={localeNativeNames[code]}
onClick={() => setLocale(code)}
>
<span class="visually-hidden">{localeNativeNames[code]}</span>
<span class="topbar__locale-flag" aria-hidden="true">
{localeFlags[code]}
</span>
</button>
)}
</For>
</div>
</header>
{props.children}
<Show when={menuOpen()}>
<div
class="nav-drawer__backdrop"
role="presentation"
onClick={() => setMenuOpen(false)}
/>
<nav
id="site-nav-drawer"
class="nav-drawer"
aria-label={t().menu}
role="dialog"
aria-modal="true"
>
<div class="nav-drawer__header">
<span class="nav-drawer__title">{t().menu}</span>
<button type="button" class="nav-drawer__close" onClick={() => setMenuOpen(false)}>
{t().menuClose}
</button>
</div>
<ul class="nav-drawer__list">
<li>
<A href="/a-propos" class="nav-drawer__link" onClick={() => setMenuOpen(false)}>
{t().navAbout}
</A>
</li>
<li>
<A href="/cgu" class="nav-drawer__link" onClick={() => setMenuOpen(false)}>
{t().navTerms}
</A>
</li>
<li>
<A href="/mentions-legales" class="nav-drawer__link" onClick={() => setMenuOpen(false)}>
{t().navLegal}
</A>
</li>
<li>
<A href="/confidentialite" class="nav-drawer__link" onClick={() => setMenuOpen(false)}>
{t().navPrivacy}
</A>
</li>
</ul>
</nav>
</Show>
<Show when={consentBannerOpen()}>
<CookieConsentBanner t={t} onAccept={acceptAnalyticsCookies} onReject={rejectAnalyticsCookies} />
</Show>
<WelcomeTourModal t={t} open={welcomeTourOpen} onDismiss={dismissWelcomeTour} />
</>
);
}
/** Enveloppe : locale partagée + barre + menu latéral. */
export function RootLayout(props: RouteSectionProps) {
return (
<AppLocaleProvider>
<RootLayoutInner {...props} />
</AppLocaleProvider>
);
}
+93
View File
@@ -0,0 +1,93 @@
import type { Accessor } from "solid-js";
import { For, Show, createEffect } from "solid-js";
import type { Dictionary } from "@ben-to/i18n/dictionary";
import { publicAssetHref } from "./builder-flow/public-asset";
export function WelcomeTourModal(props: {
readonly t: Accessor<Dictionary>;
readonly open: Accessor<boolean>;
readonly onDismiss: () => void;
}) {
let primaryButton: HTMLButtonElement | undefined;
createEffect(() => {
if (!props.open()) return;
queueMicrotask(() => primaryButton?.focus());
});
const bullets = () =>
[
props.t().welcomeTourBulletSteps,
props.t().welcomeTourBulletFilters,
props.t().welcomeTourBulletServings,
props.t().welcomeTourBulletShare
] as const;
return (
<Show when={props.open()}>
<div
class="welcome-tour__backdrop"
role="presentation"
aria-hidden="true"
onClick={() => props.onDismiss()}
/>
<div
id="welcome-tour-dialog"
class="welcome-tour"
role="dialog"
aria-modal="true"
aria-labelledby="welcome-tour-heading"
aria-describedby="welcome-tour-intro"
>
<div class="welcome-tour__inner">
<h2 id="welcome-tour-heading" class="welcome-tour__title">
{props.t().welcomeTourTitle}
</h2>
<p id="welcome-tour-intro" class="welcome-tour__intro">
{props.t().welcomeTourIntro}
</p>
<ul class="welcome-tour__bullets">
<For each={bullets()}>{(text) => <li>{text}</li>}</For>
</ul>
<div class="welcome-tour__gifs">
<figure class="welcome-tour__figure">
<img
class="welcome-tour__gif"
src={publicAssetHref("/tour/flow-builder.gif")}
alt={props.t().welcomeTourGifFlowAlt}
width={360}
height={202}
loading="lazy"
decoding="async"
/>
</figure>
<figure class="welcome-tour__figure">
<img
class="welcome-tour__gif"
src={publicAssetHref("/tour/recipe-sheet.gif")}
alt={props.t().welcomeTourGifRecipeAlt}
width={360}
height={202}
loading="lazy"
decoding="async"
/>
</figure>
</div>
<div class="welcome-tour__actions">
<button
type="button"
class="welcome-tour__btn welcome-tour__btn--primary"
ref={(el) => (primaryButton = el)}
onClick={() => props.onDismiss()}
>
{props.t().welcomeTourCtaStart}
</button>
<button type="button" class="welcome-tour__btn welcome-tour__btn--ghost" onClick={() => props.onDismiss()}>
{props.t().welcomeTourCtaClose}
</button>
</div>
</div>
</div>
</Show>
);
}
+26
View File
@@ -0,0 +1,26 @@
import { createContext, createSignal, useContext, type ParentProps } from "solid-js";
import type { LocaleCode } from "@ben-to/i18n/dictionary";
export type AppLocaleValue = Readonly<{
locale: () => LocaleCode;
setLocale: (code: LocaleCode) => void;
}>;
const AppLocaleContext = createContext<AppLocaleValue>();
export function AppLocaleProvider(props: ParentProps) {
const [locale, setLocale] = createSignal<LocaleCode>("fr");
const value: AppLocaleValue = {
locale,
setLocale
};
return <AppLocaleContext.Provider value={value}>{props.children}</AppLocaleContext.Provider>;
}
export function useAppLocale(): AppLocaleValue {
const ctx = useContext(AppLocaleContext);
if (!ctx) {
throw new Error("useAppLocale doit être utilisé sous AppLocaleProvider");
}
return ctx;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,84 @@
import type { Accessor, Resource, Setter } from "solid-js";
import { For, Show } from "solid-js";
import type { BaseOption, BentoSelection } from "@ben-to/builder/domain";
import type { RecipesIndex } from "@ben-to/recipes/catalog";
import type { LocaleCode } from "@ben-to/i18n/dictionary";
import type { DictionaryView } from "./dictionary-view";
import type { FlavorFilter } from "./builder-flow-types";
import { SpriteTile } from "./SpriteTile";
import { publicAssetHref } from "./public-asset";
export function BuilderBaseStep(props: {
readonly t: Accessor<DictionaryView>;
readonly locale: Accessor<LocaleCode>;
readonly recipesIndex: Resource<RecipesIndex | undefined>;
readonly filteredBases: Accessor<BaseOption[]>;
readonly flavorFilter: Accessor<FlavorFilter>;
readonly setFlavorFilter: Setter<FlavorFilter>;
readonly selection: Accessor<BentoSelection>;
readonly setSelection: Setter<BentoSelection>;
readonly advanceAfterBasePickDouble: () => void;
readonly onPickCardTouchEndForDouble: (
cardKey: string,
advance: () => void
) => (e: TouchEvent) => void;
}) {
return (
<section aria-label={props.t().selectBase}>
<div class="filters">
<button
type="button"
class={props.flavorFilter() === "all" ? "chip selected" : "chip"}
onClick={() => props.setFlavorFilter("all")}
>
{props.t().filterAll}
</button>
<button
type="button"
class={props.flavorFilter() === "savory" ? "chip selected" : "chip"}
onClick={() => props.setFlavorFilter("savory")}
>
{props.t().filterSavory}
</button>
<button
type="button"
class={props.flavorFilter() === "sweet" ? "chip selected" : "chip"}
onClick={() => props.setFlavorFilter("sweet")}
>
{props.t().filterSweet}
</button>
</div>
<Show when={props.recipesIndex.loading}>
<p>{props.t().loading}</p>
</Show>
<Show when={props.recipesIndex.error}>
<p role="alert">{props.t().loadError}</p>
</Show>
<div class="cards">
<For each={props.filteredBases()}>
{(base) => (
<button
type="button"
class={`card card--pick ${props.selection().baseId === base.id ? "selected" : ""}`}
onClick={() => props.setSelection((prev) => ({ ...prev, baseId: base.id, variantId: null }))}
onDblClick={() => props.advanceAfterBasePickDouble()}
onTouchEnd={props.onPickCardTouchEndForDouble(`base:${base.id}`, props.advanceAfterBasePickDouble)}
>
<Show when={base.coverImageUrl}>
<img class="card__pick-icon" src={publicAssetHref(base.coverImageUrl)} alt="" />
</Show>
<Show when={!base.coverImageUrl && base.coverSprite}>
<SpriteTile
sprite={base.coverSprite!}
size={32}
class="card__sprite card__pick-sprite"
/>
</Show>
<span class="card__pick-name">{base.name[props.locale()]}</span>
</button>
)}
</For>
</div>
</section>
);
}
@@ -0,0 +1,122 @@
import type { Accessor } from "solid-js";
import { createEffect, onCleanup, Show } from "solid-js";
import type { BaseOption, StepKind, VariantOption } from "@ben-to/builder/domain";
import type { LocaleCode } from "@ben-to/i18n/dictionary";
import type { DictionaryView } from "./dictionary-view";
import { SpriteTile } from "./SpriteTile";
import { publicAssetHref } from "./public-asset";
export function BuilderCtaDock(props: {
readonly canContinue: Accessor<boolean>;
readonly currentStep: Accessor<StepKind>;
readonly currentBase: Accessor<BaseOption | undefined>;
readonly currentVariant: Accessor<VariantOption | undefined>;
readonly locale: Accessor<LocaleCode>;
readonly t: Accessor<DictionaryView>;
readonly nextStep: () => void;
}) {
let dockEl: HTMLDivElement | undefined;
let dockObserver: ResizeObserver | undefined;
const updateDockHeight = () => {
document.documentElement.style.setProperty(
"--cta-dock-height",
props.canContinue() && dockEl ? `${Math.ceil(dockEl.getBoundingClientRect().height)}px` : "0px"
);
};
const observeDock = () => {
dockObserver?.disconnect();
if (!props.canContinue() || !dockEl) return;
dockObserver = new ResizeObserver(updateDockHeight);
dockObserver.observe(dockEl);
updateDockHeight();
};
createEffect(() => {
if (!props.canContinue()) {
dockObserver?.disconnect();
dockObserver = undefined;
document.documentElement.style.setProperty("--cta-dock-height", "0px");
return;
}
observeDock();
});
onCleanup(() => {
dockObserver?.disconnect();
document.documentElement.style.removeProperty("--cta-dock-height");
});
return (
<Show when={props.canContinue()}>
<div
class="cta-dock"
role="presentation"
ref={(el) => {
dockEl = el;
observeDock();
}}
>
<div class="cta-dock__inner">
<Show when={props.currentStep() === "base" && props.currentBase()} keyed>
{(base: BaseOption) => {
const hasMedia = Boolean(base.coverImageUrl || base.coverSprite);
return (
<div class="cta-context" classList={{ "cta-context--with-media": hasMedia }}>
{base.coverImageUrl ? (
<div class="cta-context__media">
<img
class="cta-context__icon"
src={publicAssetHref(base.coverImageUrl)}
alt=""
width="32"
height="32"
/>
</div>
) : base.coverSprite ? (
<div class="cta-context__media">
<SpriteTile sprite={base.coverSprite} size={32} class="cta-context__sprite" />
</div>
) : null}
<p class="cta-context__title">{base.name[props.locale()]}</p>
<p class="cta-context__subtitle">{base.history[props.locale()]}</p>
</div>
);
}}
</Show>
<Show when={props.currentStep() === "variant" && props.currentVariant()} keyed>
{(variant: VariantOption) => {
const hasMedia = Boolean(variant.coverImageUrl || variant.coverSprite);
return (
<div class="cta-context" classList={{ "cta-context--with-media": hasMedia }}>
{variant.coverImageUrl ? (
<div class="cta-context__media">
<img
class="cta-context__icon"
src={publicAssetHref(variant.coverImageUrl)}
alt=""
width="32"
height="32"
/>
</div>
) : variant.coverSprite ? (
<div class="cta-context__media">
<SpriteTile sprite={variant.coverSprite} size={32} class="cta-context__sprite" />
</div>
) : null}
<p class="cta-context__title">{variant.name[props.locale()]}</p>
<p class="cta-context__subtitle">{variant.story[props.locale()]}</p>
</div>
);
}}
</Show>
<button type="button" class="cta" onClick={props.nextStep}>
{props.t().continue}
</button>
</div>
</div>
</Show>
);
}
@@ -0,0 +1,93 @@
import type { Accessor } from "solid-js";
import type { StepKind } from "@ben-to/builder/domain";
import type { DictionaryView } from "./dictionary-view";
export function BuilderStepsNav(props: {
readonly t: Accessor<DictionaryView>;
readonly progressStepIndex: Accessor<number>;
readonly progressPercent: Accessor<number>;
readonly canNavigateToStep: (step: StepKind) => boolean;
readonly goToBuilderStep: (step: StepKind) => void;
readonly currentStep: Accessor<StepKind>;
readonly recipeProgressCompact: Accessor<boolean>;
}) {
return (
<nav
class="builder-steps"
classList={{
"builder-steps--compact": props.currentStep() === "recipe" && props.recipeProgressCompact()
}}
aria-label={props.t().progress}
>
<ol class="builder-steps__list">
<li
class="builder-steps__step"
classList={{
"builder-steps__step--done": props.progressStepIndex() > 0,
"builder-steps__step--current": props.progressStepIndex() === 0
}}
aria-current={props.progressStepIndex() === 0 ? "step" : undefined}
>
<button
type="button"
class="builder-steps__badge"
aria-label={props.t().builderNavToBase}
onClick={() => props.goToBuilderStep("base")}
>
1
</button>
<span class="builder-steps__label">{props.t().builderStepBase}</span>
</li>
<li
class="builder-steps__step"
classList={{
"builder-steps__step--done": props.progressStepIndex() > 1,
"builder-steps__step--current": props.progressStepIndex() === 1
}}
aria-current={props.progressStepIndex() === 1 ? "step" : undefined}
>
<button
type="button"
class="builder-steps__badge"
disabled={!props.canNavigateToStep("variant")}
aria-label={
props.canNavigateToStep("variant")
? props.t().builderNavToVariant
: props.t().builderNavVariantDisabled
}
onClick={() => props.goToBuilderStep("variant")}
>
2
</button>
<span class="builder-steps__label">{props.t().builderStepVariant}</span>
</li>
<li
class="builder-steps__step"
classList={{
"builder-steps__step--done": props.progressStepIndex() > 2,
"builder-steps__step--current": props.progressStepIndex() === 2
}}
aria-current={props.progressStepIndex() === 2 ? "step" : undefined}
>
<button
type="button"
class="builder-steps__badge"
disabled={!props.canNavigateToStep("recipe")}
aria-label={
props.canNavigateToStep("recipe")
? props.t().builderNavToRecipe
: props.t().builderNavRecipeDisabled
}
onClick={() => props.goToBuilderStep("recipe")}
>
3
</button>
<span class="builder-steps__label">{props.t().builderStepRecipe}</span>
</li>
</ol>
<div class="builder-steps__track" aria-hidden="true">
<div class="builder-steps__fill" style={{ width: `${props.progressPercent()}%` }} />
</div>
</nav>
);
}
@@ -0,0 +1,89 @@
import { For, Show } from "solid-js";
import type { Accessor, Setter } from "solid-js";
import type { BaseOption, BentoSelection, VariantOption } from "@ben-to/builder/domain";
import { primaryBaseIdForVariant } from "@ben-to/builder/domain";
import type { LocaleCode } from "@ben-to/i18n/dictionary";
import type { DictionaryView } from "./dictionary-view";
import { SpriteTile } from "./SpriteTile";
import { publicAssetHref } from "./public-asset";
export function BuilderVariantStep(props: {
readonly t: Accessor<DictionaryView>;
readonly locale: Accessor<LocaleCode>;
readonly currentBase: Accessor<BaseOption | undefined>;
readonly availableVariants: Accessor<VariantOption[]>;
readonly selection: () => BentoSelection;
readonly setSelection: Setter<BentoSelection>;
readonly advanceAfterVariantPickDouble: () => void;
readonly onPickCardTouchEndForDouble: (
cardKey: string,
advance: () => void
) => (e: TouchEvent) => void;
}) {
return (
<section aria-label={props.t().selectVariant}>
<Show when={props.currentBase()}>
{(base) => (
<p class="builder-variant-base-reminder" role="status">
<span class="builder-variant-base-reminder__media" aria-hidden="true">
<Show when={base().coverImageUrl}>
<img
class="builder-variant-base-reminder__icon"
src={publicAssetHref(base().coverImageUrl)}
width="28"
height="28"
alt=""
decoding="async"
/>
</Show>
<Show when={!base().coverImageUrl && base().coverSprite}>
<SpriteTile
sprite={base().coverSprite!}
size={28}
class="builder-variant-base-reminder__sprite"
/>
</Show>
</span>
<span class="builder-variant-base-reminder__text">
{props.t().builderVariantBaseReminderTemplate.replace("{base}", base().name[props.locale()])}
</span>
</p>
)}
</Show>
<div class="cards">
<For each={props.availableVariants()}>
{(variant) => (
<button
type="button"
class={`card card--pick ${props.selection().variantId === variant.id ? "selected" : ""}`}
onClick={() =>
props.setSelection((prev) => ({
...prev,
variantId: variant.id,
baseId: primaryBaseIdForVariant(variant, prev.baseId)
}))
}
onDblClick={() => props.advanceAfterVariantPickDouble()}
onTouchEnd={props.onPickCardTouchEndForDouble(
`variant:${variant.id}`,
props.advanceAfterVariantPickDouble
)}
>
<Show when={variant.coverImageUrl}>
<img class="card__pick-icon" src={publicAssetHref(variant.coverImageUrl)} alt="" />
</Show>
<Show when={!variant.coverImageUrl && variant.coverSprite}>
<SpriteTile
sprite={variant.coverSprite!}
size={32}
class="card__sprite card__pick-sprite"
/>
</Show>
<span class="card__pick-name">{variant.name[props.locale()]}</span>
</button>
)}
</For>
</div>
</section>
);
}
@@ -0,0 +1,190 @@
import type { Accessor } from "solid-js";
import { For, Show } from "solid-js";
import type { StepKind } from "@ben-to/builder/domain";
import { computeRecipeSteps } from "@ben-to/builder/domain";
import type { LocaleCode } from "@ben-to/i18n/dictionary";
import type { DictionaryView } from "./dictionary-view";
import { fillRecipeTemplate } from "./recipe-template";
import { SpriteTile } from "./SpriteTile";
import { publicAssetHref } from "./public-asset";
import type { RecipeSheetHandle } from "./use-recipe-sheet";
type MergedRecipeView = ReturnType<typeof computeRecipeSteps>;
export function RecipeSheetSection(props: {
readonly currentStep: Accessor<StepKind>;
readonly locale: Accessor<LocaleCode>;
readonly t: Accessor<DictionaryView>;
readonly recipeCoverImageUrl: Accessor<string | null>;
readonly recipeDisplayTitle: Accessor<string>;
readonly mergedRecipe: Accessor<MergedRecipeView>;
readonly recipe: RecipeSheetHandle;
}) {
const r = props.recipe;
return (
<Show when={props.currentStep() === "recipe"}>
<div
class="recipe-scroll"
ref={(el) => {
r.bindRecipeScrollHost(el);
}}
onScroll={r.onRecipeScroll}
>
<section class="recipe-sheet" aria-labelledby="recipe-sheet-title">
<Show when={props.recipeCoverImageUrl()}>
<div class="recipe-sheet__hero">
<div class="recipe-sheet__hero-media">
<img
class="recipe-sheet__photo"
src={publicAssetHref(props.recipeCoverImageUrl())}
alt={props.recipeDisplayTitle() || props.t().recipe}
width="1200"
height="675"
decoding="async"
fetchpriority="high"
/>
</div>
</div>
</Show>
<h2 class="recipe-sheet__title" id="recipe-sheet-title">
{props.recipeDisplayTitle() || props.t().recipe}
</h2>
<div class="recipe-sheet__actions">
<button type="button" class="recipe-sheet__action" onClick={() => void r.shareRecipe()}>
{props.t().shareRecipe}
</button>
<button type="button" class="recipe-sheet__action" onClick={r.printRecipe}>
{props.t().printRecipe}
</button>
</div>
<Show when={r.shareFeedback()}>
<p class="recipe-sheet__share-feedback" role="status" aria-live="polite">
{r.shareFeedback()}
</p>
</Show>
<div class="servings-stepper" role="group" aria-label={props.t().servings}>
<button
type="button"
class="servings-stepper__btn"
aria-label={props.t().decreaseServings}
disabled={r.servings() <= 1}
onClick={() => r.bumpServings(-1)}
>
</button>
<label class="servings-stepper__field">
<span class="visually-hidden">{props.t().servings}</span>
<input
class="servings-stepper__input"
type="text"
inputMode="numeric"
autocomplete="off"
maxLength={2}
value={r.servingsFieldValue()}
onFocus={r.onServingsFocus}
onInput={r.onServingsInput}
onBlur={r.onServingsBlur}
/>
</label>
<button
type="button"
class="servings-stepper__btn"
aria-label={props.t().increaseServings}
disabled={r.servings() >= 99}
onClick={() => r.bumpServings(1)}
>
+
</button>
</div>
<p class="recipe-sheet__print-servings" aria-hidden="true">
{props.t().servings}: {r.servings()}
</p>
<h3 class="recipe-sheet__subtitle">{props.t().ingredients}</h3>
<ul class="recipe-sheet__ingredient-list">
<For each={r.displayedIngredients()}>
{(ingredient) => (
<li class="recipe-sheet__ingredient-item">
<button
type="button"
class="recipe-sheet__ingredient-row"
classList={{
"recipe-sheet__ingredient-row--done": r.checkedIngredientIds().has(ingredient.id)
}}
aria-pressed={r.checkedIngredientIds().has(ingredient.id)}
title={[
fillRecipeTemplate(props.t().recipeIngredientCheckboxLabel, {
name: ingredient.name[props.locale()]
}),
String(ingredient.info[props.locale()] ?? "").trim()
]
.filter(Boolean)
.join(" — ")}
onClick={() => r.toggleIngredientChecklist(ingredient.id)}
>
<span class="recipe-sheet__ingredient-media" aria-hidden="true">
{ingredient.coverSprite ? (
<SpriteTile
sprite={ingredient.coverSprite}
size={32}
class="recipe-sheet__ingredient-sprite"
/>
) : (
<img
class="recipe-sheet__ingredient-fallback"
src={publicAssetHref("/ingredient-unknown.png")}
width="32"
height="32"
alt=""
decoding="async"
/>
)}
</span>
<span class="recipe-sheet__ingredient-text">
<strong>
{ingredient.quantity}
{ingredient.unit ? ` ${ingredient.unit}` : ""}
</strong>{" "}
{ingredient.name[props.locale()]}
</span>
<span class="recipe-sheet__ingredient-row-check" aria-hidden="true">
<span class="recipe-sheet__ingredient-mark__glyph"></span>
</span>
</button>
</li>
)}
</For>
</ul>
<h3 class="recipe-sheet__subtitle">{props.t().steps}</h3>
<ol class="recipe-sheet__steps-list">
<For each={props.mergedRecipe().steps}>
{(step, index) => (
<li class="recipe-sheet__step-item">
<button
type="button"
class="recipe-sheet__step-btn"
classList={{
"recipe-sheet__step-btn--done": r.doneStepIds().has(step.id)
}}
aria-pressed={r.doneStepIds().has(step.id)}
title={fillRecipeTemplate(props.t().recipeStepToggleLabel, {
n: index() + 1
})}
onClick={() => r.toggleStepChecklist(step.id)}
>
{step.text[props.locale()]}
</button>
</li>
)}
</For>
</ol>
</section>
</div>
</Show>
);
}

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