Commit ·
2992997
0
Parent(s):
Deploy PraxaLing: component refactor, bug fixes, unified neo-brutal design, Qwen3.5 everywhere
Browse filesSquashed deploy snapshot of main (19712cf); large binaries stored via LFS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This view is limited to 50 files because it contains too many changes. See raw diff
- .claude/scheduled_tasks.lock +1 -0
- .claude/settings.local.json +56 -0
- .dockerignore +24 -0
- .gitattributes +2 -0
- .gitignore +42 -0
- .idea/.gitignore +3 -0
- .idea/caches/deviceStreaming.xml +1857 -0
- .idea/deviceManager.xml +13 -0
- .idea/language-learner.iml +15 -0
- .idea/libraries/Dart_Packages.xml +972 -0
- .idea/libraries/Dart_SDK.xml +31 -0
- .idea/markdown.xml +8 -0
- .idea/misc.xml +5 -0
- .idea/modules.xml +8 -0
- .idea/vcs.xml +6 -0
- .mcp.json +11 -0
- AGENTS.md +5 -0
- CLAUDE.md +1 -0
- Dockerfile +48 -0
- README.md +109 -0
- app/(app)/BottomNavLink.tsx +24 -0
- app/(app)/camera/AnalysisResults.tsx +78 -0
- app/(app)/camera/CameraClient.tsx +153 -0
- app/(app)/camera/PhotoMarkers.tsx +82 -0
- app/(app)/camera/UploadDropzone.tsx +29 -0
- app/(app)/camera/page.tsx +36 -0
- app/(app)/camera/types.ts +25 -0
- app/(app)/error.tsx +17 -0
- app/(app)/layout.tsx +42 -0
- app/(app)/phrases/PhraseResult/EmptyState.tsx +35 -0
- app/(app)/phrases/PhraseResult/HeaderCard.tsx +33 -0
- app/(app)/phrases/PhraseResult/KanjiCard.tsx +39 -0
- app/(app)/phrases/PhraseResult/RelatedWordCard.tsx +25 -0
- app/(app)/phrases/PhraseResult/Section.tsx +19 -0
- app/(app)/phrases/PhraseResult/SentenceCard.tsx +20 -0
- app/(app)/phrases/PhraseResult/index.tsx +78 -0
- app/(app)/phrases/PhrasesClient.tsx +100 -0
- app/(app)/phrases/page.tsx +36 -0
- app/(app)/practice/CustomTopicCard.tsx +72 -0
- app/(app)/practice/DialoguePracticeClient.tsx +84 -0
- app/(app)/practice/DialogueResultView.tsx +30 -0
- app/(app)/practice/GeneratingDialogueProgress.tsx +111 -0
- app/(app)/practice/ScenarioCard.tsx +50 -0
- app/(app)/practice/ScenarioLibrary.tsx +37 -0
- app/(app)/practice/StageItem.tsx +64 -0
- app/(app)/practice/[storyId]/StoryLoader.tsx +17 -0
- app/(app)/practice/[storyId]/StoryQuiz.tsx +162 -0
- app/(app)/practice/[storyId]/StoryReader.tsx +86 -0
- app/(app)/practice/[storyId]/StoryText.tsx +43 -0
- app/(app)/practice/[storyId]/VocabList.tsx +18 -0
.claude/scheduled_tasks.lock
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"sessionId":"6e5f9b33-6766-4a70-8673-bd559c8270cb","pid":16372,"acquiredAt":1778656145073}
|
.claude/settings.local.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"permissions": {
|
| 3 |
+
"allow": [
|
| 4 |
+
"WebFetch(domain:api.anthropic.com)",
|
| 5 |
+
"Bash(tar -xf bundle)",
|
| 6 |
+
"Bash(npx tsc *)",
|
| 7 |
+
"Bash(echo \"EXIT=$?\")",
|
| 8 |
+
"Bash(npx next *)",
|
| 9 |
+
"Bash(npx eslint *)",
|
| 10 |
+
"Bash(git restore *)",
|
| 11 |
+
"Bash(git reset *)",
|
| 12 |
+
"Bash(Get-ChildItem -Path \"E:\\\\language-learner\\\\mobile\" -Recurse -Include \"pubspec.yaml\", \"*.yaml\", \"*.yml\")",
|
| 13 |
+
"Bash(Select-Object FullName)",
|
| 14 |
+
"Bash(Get-ChildItem -Path \"E:\\\\language-learner\\\\node_modules\\\\next\\\\dist\\\\docs\" -ErrorAction SilentlyContinue)",
|
| 15 |
+
"Bash(Select-Object -First 20)",
|
| 16 |
+
"Bash(npm install *)",
|
| 17 |
+
"Bash(Get-Content \"E:\\\\language-learner\\\\mobile\\\\lib\\\\features\\\\home\\\\home_shell.dart\")",
|
| 18 |
+
"Bash(Select-Object -First 3)",
|
| 19 |
+
"Bash(npm run *)",
|
| 20 |
+
"mcp__puppeteer__puppeteer_navigate",
|
| 21 |
+
"mcp__puppeteer__puppeteer_screenshot",
|
| 22 |
+
"Bash(taskkill /PID 24984 /F)",
|
| 23 |
+
"PowerShell(Stop-Process *)",
|
| 24 |
+
"PowerShell(Get-Process *)",
|
| 25 |
+
"Read(//tmp/**)",
|
| 26 |
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000/)",
|
| 27 |
+
"Bash(curl -s -o /dev/null -w \"%{http_code}\" http://localhost:3000/onboarding)",
|
| 28 |
+
"Bash(curl -s -o /dev/null -w \" | /camera: %{http_code}\" http://localhost:3000/camera)",
|
| 29 |
+
"Bash(git add *)",
|
| 30 |
+
"Bash(git commit -m ' *)",
|
| 31 |
+
"Bash(git push *)",
|
| 32 |
+
"PowerShell(git push *)",
|
| 33 |
+
"Bash(taskkill *)",
|
| 34 |
+
"Bash(git *)",
|
| 35 |
+
"Bash(npm ls *)",
|
| 36 |
+
"Bash(findstr /i \"huggingface inference\")",
|
| 37 |
+
"mcp__claude_ai_Hugging_Face__hub_repo_details",
|
| 38 |
+
"mcp__claude_ai_Hugging_Face__hub_repo_search",
|
| 39 |
+
"Bash(curl -s -o NUL -w \"practice %{http_code}\\\\n\" http://localhost:3000/practice)",
|
| 40 |
+
"Bash(curl -s -o NUL -w \"phrases %{http_code}\\\\n\" http://localhost:3000/phrases)",
|
| 41 |
+
"Bash(curl -s -o NUL -w \"camera %{http_code}\\\\n\" http://localhost:3000/camera)",
|
| 42 |
+
"Bash(npx --no -- tsc --noEmit --jsx preserve --esModuleInterop --skipLibCheck app/onboarding/OnboardingForm.tsx)",
|
| 43 |
+
"Bash(Get-ChildItem -Path \"E:\\\\language-learner\\\\mobile\\\\lib\" -Recurse -File)",
|
| 44 |
+
"Bash(Sort-Object FullName)",
|
| 45 |
+
"Bash(Get-ChildItem -Path \"E:\\\\language-learner\\\\app\" -Recurse -Directory)",
|
| 46 |
+
"Bash(Select-Object -First 30 FullName)",
|
| 47 |
+
"Bash(grep -E \"\\\\.\\(tsx?|json|mjs\\)$\")",
|
| 48 |
+
"Bash(xargs wc -l)",
|
| 49 |
+
"Bash(flutter analyze *)",
|
| 50 |
+
"Bash(ls *)",
|
| 51 |
+
"Bash(xargs grep -l \"novita\")",
|
| 52 |
+
"Bash(echo \"EXIT_CODE=$?\")"
|
| 53 |
+
]
|
| 54 |
+
},
|
| 55 |
+
"outputStyle": "default"
|
| 56 |
+
}
|
.dockerignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.git
|
| 2 |
+
.gitignore
|
| 3 |
+
.github
|
| 4 |
+
.next
|
| 5 |
+
node_modules
|
| 6 |
+
npm-debug.log
|
| 7 |
+
.env
|
| 8 |
+
.env.local
|
| 9 |
+
.env.*.local
|
| 10 |
+
.vscode
|
| 11 |
+
.idea
|
| 12 |
+
.DS_Store
|
| 13 |
+
|
| 14 |
+
# Mobile app — not part of the web build
|
| 15 |
+
mobile
|
| 16 |
+
|
| 17 |
+
# Claude / local working dirs
|
| 18 |
+
.claude
|
| 19 |
+
docs
|
| 20 |
+
|
| 21 |
+
# Build artifacts you don't need in the container
|
| 22 |
+
tsconfig.tsbuildinfo
|
| 23 |
+
response.html
|
| 24 |
+
*.log
|
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
logo.png filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
mobile/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
| 2 |
+
|
| 3 |
+
# dependencies
|
| 4 |
+
/node_modules
|
| 5 |
+
/.pnp
|
| 6 |
+
.pnp.*
|
| 7 |
+
.yarn/*
|
| 8 |
+
!.yarn/patches
|
| 9 |
+
!.yarn/plugins
|
| 10 |
+
!.yarn/releases
|
| 11 |
+
!.yarn/versions
|
| 12 |
+
|
| 13 |
+
# testing
|
| 14 |
+
/coverage
|
| 15 |
+
|
| 16 |
+
# next.js
|
| 17 |
+
/.next/
|
| 18 |
+
/out/
|
| 19 |
+
|
| 20 |
+
# production
|
| 21 |
+
/build
|
| 22 |
+
|
| 23 |
+
# misc
|
| 24 |
+
.DS_Store
|
| 25 |
+
*.pem
|
| 26 |
+
|
| 27 |
+
# debug
|
| 28 |
+
npm-debug.log*
|
| 29 |
+
yarn-debug.log*
|
| 30 |
+
yarn-error.log*
|
| 31 |
+
.pnpm-debug.log*
|
| 32 |
+
|
| 33 |
+
# env files (can opt-in for committing if needed)
|
| 34 |
+
.env*
|
| 35 |
+
!.env.example
|
| 36 |
+
|
| 37 |
+
# vercel
|
| 38 |
+
.vercel
|
| 39 |
+
|
| 40 |
+
# typescript
|
| 41 |
+
*.tsbuildinfo
|
| 42 |
+
next-env.d.ts
|
.idea/.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default ignored files
|
| 2 |
+
/shelf/
|
| 3 |
+
/workspace.xml
|
.idea/caches/deviceStreaming.xml
ADDED
|
@@ -0,0 +1,1857 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="DeviceStreaming">
|
| 4 |
+
<option name="deviceSelectionList">
|
| 5 |
+
<list>
|
| 6 |
+
<PersistentDeviceSelectionData>
|
| 7 |
+
<option name="api" value="34" />
|
| 8 |
+
<option name="brand" value="Sony" />
|
| 9 |
+
<option name="codename" value="A402SO" />
|
| 10 |
+
<option name="id" value="A402SO" />
|
| 11 |
+
<option name="labId" value="google" />
|
| 12 |
+
<option name="manufacturer" value="Sony" />
|
| 13 |
+
<option name="name" value="Xperia 10 VI" />
|
| 14 |
+
<option name="screenDensity" value="450" />
|
| 15 |
+
<option name="screenX" value="1080" />
|
| 16 |
+
<option name="screenY" value="2520" />
|
| 17 |
+
</PersistentDeviceSelectionData>
|
| 18 |
+
<PersistentDeviceSelectionData>
|
| 19 |
+
<option name="api" value="27" />
|
| 20 |
+
<option name="brand" value="DOCOMO" />
|
| 21 |
+
<option name="codename" value="F01L" />
|
| 22 |
+
<option name="id" value="F01L" />
|
| 23 |
+
<option name="labId" value="google" />
|
| 24 |
+
<option name="manufacturer" value="FUJITSU" />
|
| 25 |
+
<option name="name" value="F-01L" />
|
| 26 |
+
<option name="screenDensity" value="360" />
|
| 27 |
+
<option name="screenX" value="720" />
|
| 28 |
+
<option name="screenY" value="1280" />
|
| 29 |
+
</PersistentDeviceSelectionData>
|
| 30 |
+
<PersistentDeviceSelectionData>
|
| 31 |
+
<option name="api" value="34" />
|
| 32 |
+
<option name="brand" value="OnePlus" />
|
| 33 |
+
<option name="codename" value="OP535DL1" />
|
| 34 |
+
<option name="id" value="OP535DL1" />
|
| 35 |
+
<option name="labId" value="google" />
|
| 36 |
+
<option name="manufacturer" value="OnePlus" />
|
| 37 |
+
<option name="name" value="Nord CE 2 Lite 5G" />
|
| 38 |
+
<option name="screenDensity" value="401" />
|
| 39 |
+
<option name="screenX" value="1080" />
|
| 40 |
+
<option name="screenY" value="2412" />
|
| 41 |
+
</PersistentDeviceSelectionData>
|
| 42 |
+
<PersistentDeviceSelectionData>
|
| 43 |
+
<option name="api" value="34" />
|
| 44 |
+
<option name="brand" value="OnePlus" />
|
| 45 |
+
<option name="codename" value="OP5552L1" />
|
| 46 |
+
<option name="id" value="OP5552L1" />
|
| 47 |
+
<option name="labId" value="google" />
|
| 48 |
+
<option name="manufacturer" value="OnePlus" />
|
| 49 |
+
<option name="name" value="10T 5G" />
|
| 50 |
+
<option name="screenDensity" value="480" />
|
| 51 |
+
<option name="screenX" value="1080" />
|
| 52 |
+
<option name="screenY" value="2412" />
|
| 53 |
+
</PersistentDeviceSelectionData>
|
| 54 |
+
<PersistentDeviceSelectionData>
|
| 55 |
+
<option name="api" value="34" />
|
| 56 |
+
<option name="brand" value="OPPO" />
|
| 57 |
+
<option name="codename" value="OP573DL1" />
|
| 58 |
+
<option name="id" value="OP573DL1" />
|
| 59 |
+
<option name="labId" value="google" />
|
| 60 |
+
<option name="manufacturer" value="OPPO" />
|
| 61 |
+
<option name="name" value="A79 5G" />
|
| 62 |
+
<option name="screenDensity" value="480" />
|
| 63 |
+
<option name="screenX" value="1080" />
|
| 64 |
+
<option name="screenY" value="2400" />
|
| 65 |
+
</PersistentDeviceSelectionData>
|
| 66 |
+
<PersistentDeviceSelectionData>
|
| 67 |
+
<option name="api" value="34" />
|
| 68 |
+
<option name="brand" value="OPPO" />
|
| 69 |
+
<option name="codename" value="OP5759L1" />
|
| 70 |
+
<option name="id" value="OP5759L1" />
|
| 71 |
+
<option name="labId" value="google" />
|
| 72 |
+
<option name="manufacturer" value="OPPO" />
|
| 73 |
+
<option name="name" value="A38" />
|
| 74 |
+
<option name="screenDensity" value="320" />
|
| 75 |
+
<option name="screenX" value="720" />
|
| 76 |
+
<option name="screenY" value="1612" />
|
| 77 |
+
</PersistentDeviceSelectionData>
|
| 78 |
+
<PersistentDeviceSelectionData>
|
| 79 |
+
<option name="api" value="35" />
|
| 80 |
+
<option name="brand" value="realme" />
|
| 81 |
+
<option name="codename" value="RE58C2" />
|
| 82 |
+
<option name="id" value="RE58C2" />
|
| 83 |
+
<option name="labId" value="google" />
|
| 84 |
+
<option name="manufacturer" value="realme" />
|
| 85 |
+
<option name="name" value="C53" />
|
| 86 |
+
<option name="screenDensity" value="320" />
|
| 87 |
+
<option name="screenX" value="720" />
|
| 88 |
+
<option name="screenY" value="1600" />
|
| 89 |
+
</PersistentDeviceSelectionData>
|
| 90 |
+
<PersistentDeviceSelectionData>
|
| 91 |
+
<option name="api" value="30" />
|
| 92 |
+
<option name="brand" value="realme" />
|
| 93 |
+
<option name="codename" value="RMX3231" />
|
| 94 |
+
<option name="id" value="RMX3231" />
|
| 95 |
+
<option name="labId" value="google" />
|
| 96 |
+
<option name="manufacturer" value="realme" />
|
| 97 |
+
<option name="name" value="RMX3231" />
|
| 98 |
+
<option name="screenDensity" value="320" />
|
| 99 |
+
<option name="screenX" value="720" />
|
| 100 |
+
<option name="screenY" value="1600" />
|
| 101 |
+
</PersistentDeviceSelectionData>
|
| 102 |
+
<PersistentDeviceSelectionData>
|
| 103 |
+
<option name="api" value="36" />
|
| 104 |
+
<option name="brand" value="samsung" />
|
| 105 |
+
<option name="codename" value="SC-53C" />
|
| 106 |
+
<option name="id" value="SC-53C" />
|
| 107 |
+
<option name="labId" value="google" />
|
| 108 |
+
<option name="manufacturer" value="Samsung" />
|
| 109 |
+
<option name="name" value="Galaxy A53 5G" />
|
| 110 |
+
<option name="screenDensity" value="450" />
|
| 111 |
+
<option name="screenX" value="1080" />
|
| 112 |
+
<option name="screenY" value="2400" />
|
| 113 |
+
</PersistentDeviceSelectionData>
|
| 114 |
+
<PersistentDeviceSelectionData>
|
| 115 |
+
<option name="api" value="28" />
|
| 116 |
+
<option name="brand" value="DOCOMO" />
|
| 117 |
+
<option name="codename" value="SH-01L" />
|
| 118 |
+
<option name="id" value="SH-01L" />
|
| 119 |
+
<option name="labId" value="google" />
|
| 120 |
+
<option name="manufacturer" value="SHARP" />
|
| 121 |
+
<option name="name" value="AQUOS sense2 SH-01L" />
|
| 122 |
+
<option name="screenDensity" value="480" />
|
| 123 |
+
<option name="screenX" value="1080" />
|
| 124 |
+
<option name="screenY" value="2160" />
|
| 125 |
+
</PersistentDeviceSelectionData>
|
| 126 |
+
<PersistentDeviceSelectionData>
|
| 127 |
+
<option name="api" value="35" />
|
| 128 |
+
<option name="brand" value="Lenovo" />
|
| 129 |
+
<option name="codename" value="TB330FU" />
|
| 130 |
+
<option name="formFactor" value="Tablet" />
|
| 131 |
+
<option name="id" value="TB330FU" />
|
| 132 |
+
<option name="labId" value="google" />
|
| 133 |
+
<option name="manufacturer" value="Lenovo" />
|
| 134 |
+
<option name="name" value="Tab M11" />
|
| 135 |
+
<option name="screenDensity" value="240" />
|
| 136 |
+
<option name="screenX" value="1200" />
|
| 137 |
+
<option name="screenY" value="1920" />
|
| 138 |
+
</PersistentDeviceSelectionData>
|
| 139 |
+
<PersistentDeviceSelectionData>
|
| 140 |
+
<option name="api" value="33" />
|
| 141 |
+
<option name="brand" value="samsung" />
|
| 142 |
+
<option name="codename" value="a03su" />
|
| 143 |
+
<option name="id" value="a03su" />
|
| 144 |
+
<option name="labId" value="google" />
|
| 145 |
+
<option name="manufacturer" value="Samsung" />
|
| 146 |
+
<option name="name" value="Galaxy A03s" />
|
| 147 |
+
<option name="screenDensity" value="280" />
|
| 148 |
+
<option name="screenX" value="720" />
|
| 149 |
+
<option name="screenY" value="1600" />
|
| 150 |
+
</PersistentDeviceSelectionData>
|
| 151 |
+
<PersistentDeviceSelectionData>
|
| 152 |
+
<option name="api" value="35" />
|
| 153 |
+
<option name="brand" value="samsung" />
|
| 154 |
+
<option name="codename" value="a05s" />
|
| 155 |
+
<option name="id" value="a05s" />
|
| 156 |
+
<option name="labId" value="google" />
|
| 157 |
+
<option name="manufacturer" value="Samsung" />
|
| 158 |
+
<option name="name" value="Galaxy A05s" />
|
| 159 |
+
<option name="screenDensity" value="450" />
|
| 160 |
+
<option name="screenX" value="1080" />
|
| 161 |
+
<option name="screenY" value="2400" />
|
| 162 |
+
</PersistentDeviceSelectionData>
|
| 163 |
+
<PersistentDeviceSelectionData>
|
| 164 |
+
<option name="api" value="35" />
|
| 165 |
+
<option name="brand" value="samsung" />
|
| 166 |
+
<option name="codename" value="a06" />
|
| 167 |
+
<option name="id" value="a06" />
|
| 168 |
+
<option name="labId" value="google" />
|
| 169 |
+
<option name="manufacturer" value="Samsung" />
|
| 170 |
+
<option name="name" value="Galaxy A06" />
|
| 171 |
+
<option name="screenDensity" value="300" />
|
| 172 |
+
<option name="screenX" value="720" />
|
| 173 |
+
<option name="screenY" value="1600" />
|
| 174 |
+
</PersistentDeviceSelectionData>
|
| 175 |
+
<PersistentDeviceSelectionData>
|
| 176 |
+
<option name="api" value="34" />
|
| 177 |
+
<option name="brand" value="samsung" />
|
| 178 |
+
<option name="codename" value="a13" />
|
| 179 |
+
<option name="id" value="a13" />
|
| 180 |
+
<option name="labId" value="google" />
|
| 181 |
+
<option name="manufacturer" value="Samsung" />
|
| 182 |
+
<option name="name" value="Galaxy A13" />
|
| 183 |
+
<option name="screenDensity" value="450" />
|
| 184 |
+
<option name="screenX" value="1080" />
|
| 185 |
+
<option name="screenY" value="2408" />
|
| 186 |
+
</PersistentDeviceSelectionData>
|
| 187 |
+
<PersistentDeviceSelectionData>
|
| 188 |
+
<option name="api" value="33" />
|
| 189 |
+
<option name="brand" value="samsung" />
|
| 190 |
+
<option name="codename" value="a13x" />
|
| 191 |
+
<option name="id" value="a13x" />
|
| 192 |
+
<option name="labId" value="google" />
|
| 193 |
+
<option name="manufacturer" value="Samsung" />
|
| 194 |
+
<option name="name" value="Galaxy A13 5G" />
|
| 195 |
+
<option name="screenDensity" value="300" />
|
| 196 |
+
<option name="screenX" value="720" />
|
| 197 |
+
<option name="screenY" value="1600" />
|
| 198 |
+
</PersistentDeviceSelectionData>
|
| 199 |
+
<PersistentDeviceSelectionData>
|
| 200 |
+
<option name="api" value="34" />
|
| 201 |
+
<option name="brand" value="samsung" />
|
| 202 |
+
<option name="codename" value="a14m" />
|
| 203 |
+
<option name="id" value="a14m" />
|
| 204 |
+
<option name="labId" value="google" />
|
| 205 |
+
<option name="manufacturer" value="Samsung" />
|
| 206 |
+
<option name="name" value="Galaxy A14" />
|
| 207 |
+
<option name="screenDensity" value="450" />
|
| 208 |
+
<option name="screenX" value="1080" />
|
| 209 |
+
<option name="screenY" value="2408" />
|
| 210 |
+
</PersistentDeviceSelectionData>
|
| 211 |
+
<PersistentDeviceSelectionData>
|
| 212 |
+
<option name="api" value="35" />
|
| 213 |
+
<option name="brand" value="samsung" />
|
| 214 |
+
<option name="codename" value="a14xmsq" />
|
| 215 |
+
<option name="id" value="a14xmsq" />
|
| 216 |
+
<option name="labId" value="google" />
|
| 217 |
+
<option name="manufacturer" value="Samsung" />
|
| 218 |
+
<option name="name" value="Galaxy A14 5G" />
|
| 219 |
+
<option name="screenDensity" value="450" />
|
| 220 |
+
<option name="screenX" value="1080" />
|
| 221 |
+
<option name="screenY" value="2408" />
|
| 222 |
+
</PersistentDeviceSelectionData>
|
| 223 |
+
<PersistentDeviceSelectionData>
|
| 224 |
+
<option name="api" value="35" />
|
| 225 |
+
<option name="brand" value="samsung" />
|
| 226 |
+
<option name="codename" value="a14xmtfn" />
|
| 227 |
+
<option name="id" value="a14xmtfn" />
|
| 228 |
+
<option name="labId" value="google" />
|
| 229 |
+
<option name="manufacturer" value="Samsung" />
|
| 230 |
+
<option name="name" value="Galaxy A14 5G" />
|
| 231 |
+
<option name="screenDensity" value="450" />
|
| 232 |
+
<option name="screenX" value="1080" />
|
| 233 |
+
<option name="screenY" value="2408" />
|
| 234 |
+
</PersistentDeviceSelectionData>
|
| 235 |
+
<PersistentDeviceSelectionData>
|
| 236 |
+
<option name="api" value="34" />
|
| 237 |
+
<option name="brand" value="samsung" />
|
| 238 |
+
<option name="codename" value="a15" />
|
| 239 |
+
<option name="id" value="a15" />
|
| 240 |
+
<option name="labId" value="google" />
|
| 241 |
+
<option name="manufacturer" value="Samsung" />
|
| 242 |
+
<option name="name" value="Galaxy A15" />
|
| 243 |
+
<option name="screenDensity" value="450" />
|
| 244 |
+
<option name="screenX" value="1080" />
|
| 245 |
+
<option name="screenY" value="2340" />
|
| 246 |
+
</PersistentDeviceSelectionData>
|
| 247 |
+
<PersistentDeviceSelectionData>
|
| 248 |
+
<option name="api" value="34" />
|
| 249 |
+
<option name="brand" value="samsung" />
|
| 250 |
+
<option name="codename" value="a15x" />
|
| 251 |
+
<option name="id" value="a15x" />
|
| 252 |
+
<option name="labId" value="google" />
|
| 253 |
+
<option name="manufacturer" value="Samsung" />
|
| 254 |
+
<option name="name" value="Galaxy A15 5G" />
|
| 255 |
+
<option name="screenDensity" value="450" />
|
| 256 |
+
<option name="screenX" value="1080" />
|
| 257 |
+
<option name="screenY" value="2340" />
|
| 258 |
+
</PersistentDeviceSelectionData>
|
| 259 |
+
<PersistentDeviceSelectionData>
|
| 260 |
+
<option name="api" value="36" />
|
| 261 |
+
<option name="brand" value="samsung" />
|
| 262 |
+
<option name="codename" value="a15x" />
|
| 263 |
+
<option name="id" value="a15x" />
|
| 264 |
+
<option name="labId" value="google" />
|
| 265 |
+
<option name="manufacturer" value="Samsung" />
|
| 266 |
+
<option name="name" value="Galaxy A15 5G" />
|
| 267 |
+
<option name="screenDensity" value="450" />
|
| 268 |
+
<option name="screenX" value="1080" />
|
| 269 |
+
<option name="screenY" value="2340" />
|
| 270 |
+
</PersistentDeviceSelectionData>
|
| 271 |
+
<PersistentDeviceSelectionData>
|
| 272 |
+
<option name="api" value="36" />
|
| 273 |
+
<option name="brand" value="samsung" />
|
| 274 |
+
<option name="codename" value="a15xtfn" />
|
| 275 |
+
<option name="id" value="a15xtfn" />
|
| 276 |
+
<option name="labId" value="google" />
|
| 277 |
+
<option name="manufacturer" value="Samsung" />
|
| 278 |
+
<option name="name" value="Galaxy A15 5G" />
|
| 279 |
+
<option name="screenDensity" value="450" />
|
| 280 |
+
<option name="screenX" value="1080" />
|
| 281 |
+
<option name="screenY" value="2340" />
|
| 282 |
+
</PersistentDeviceSelectionData>
|
| 283 |
+
<PersistentDeviceSelectionData>
|
| 284 |
+
<option name="api" value="35" />
|
| 285 |
+
<option name="brand" value="samsung" />
|
| 286 |
+
<option name="codename" value="a16" />
|
| 287 |
+
<option name="id" value="a16" />
|
| 288 |
+
<option name="labId" value="google" />
|
| 289 |
+
<option name="manufacturer" value="Samsung" />
|
| 290 |
+
<option name="name" value="Galaxy A16" />
|
| 291 |
+
<option name="screenDensity" value="450" />
|
| 292 |
+
<option name="screenX" value="1080" />
|
| 293 |
+
<option name="screenY" value="2340" />
|
| 294 |
+
</PersistentDeviceSelectionData>
|
| 295 |
+
<PersistentDeviceSelectionData>
|
| 296 |
+
<option name="api" value="34" />
|
| 297 |
+
<option name="brand" value="samsung" />
|
| 298 |
+
<option name="codename" value="a16x" />
|
| 299 |
+
<option name="id" value="a16x" />
|
| 300 |
+
<option name="labId" value="google" />
|
| 301 |
+
<option name="manufacturer" value="Samsung" />
|
| 302 |
+
<option name="name" value="Galaxy A16 5G" />
|
| 303 |
+
<option name="screenDensity" value="450" />
|
| 304 |
+
<option name="screenX" value="1080" />
|
| 305 |
+
<option name="screenY" value="2340" />
|
| 306 |
+
</PersistentDeviceSelectionData>
|
| 307 |
+
<PersistentDeviceSelectionData>
|
| 308 |
+
<option name="api" value="36" />
|
| 309 |
+
<option name="brand" value="samsung" />
|
| 310 |
+
<option name="codename" value="a16x" />
|
| 311 |
+
<option name="id" value="a16x" />
|
| 312 |
+
<option name="labId" value="google" />
|
| 313 |
+
<option name="manufacturer" value="Samsung" />
|
| 314 |
+
<option name="name" value="Galaxy A16 5G" />
|
| 315 |
+
<option name="screenDensity" value="450" />
|
| 316 |
+
<option name="screenX" value="1080" />
|
| 317 |
+
<option name="screenY" value="2340" />
|
| 318 |
+
</PersistentDeviceSelectionData>
|
| 319 |
+
<PersistentDeviceSelectionData>
|
| 320 |
+
<option name="api" value="35" />
|
| 321 |
+
<option name="brand" value="samsung" />
|
| 322 |
+
<option name="codename" value="a16xeea" />
|
| 323 |
+
<option name="id" value="a16xeea" />
|
| 324 |
+
<option name="labId" value="google" />
|
| 325 |
+
<option name="manufacturer" value="Samsung" />
|
| 326 |
+
<option name="name" value="Galaxy A16 5G" />
|
| 327 |
+
<option name="screenDensity" value="450" />
|
| 328 |
+
<option name="screenX" value="1080" />
|
| 329 |
+
<option name="screenY" value="2340" />
|
| 330 |
+
</PersistentDeviceSelectionData>
|
| 331 |
+
<PersistentDeviceSelectionData>
|
| 332 |
+
<option name="api" value="31" />
|
| 333 |
+
<option name="brand" value="samsung" />
|
| 334 |
+
<option name="codename" value="a21" />
|
| 335 |
+
<option name="id" value="a21" />
|
| 336 |
+
<option name="labId" value="google" />
|
| 337 |
+
<option name="manufacturer" value="Samsung" />
|
| 338 |
+
<option name="name" value="Galaxy A21" />
|
| 339 |
+
<option name="screenDensity" value="300" />
|
| 340 |
+
<option name="screenX" value="720" />
|
| 341 |
+
<option name="screenY" value="1600" />
|
| 342 |
+
</PersistentDeviceSelectionData>
|
| 343 |
+
<PersistentDeviceSelectionData>
|
| 344 |
+
<option name="api" value="36" />
|
| 345 |
+
<option name="brand" value="samsung" />
|
| 346 |
+
<option name="codename" value="a26x" />
|
| 347 |
+
<option name="id" value="a26x" />
|
| 348 |
+
<option name="labId" value="google" />
|
| 349 |
+
<option name="manufacturer" value="Samsung" />
|
| 350 |
+
<option name="name" value="Galaxy A26 5G" />
|
| 351 |
+
<option name="screenDensity" value="450" />
|
| 352 |
+
<option name="screenX" value="1080" />
|
| 353 |
+
<option name="screenY" value="2340" />
|
| 354 |
+
</PersistentDeviceSelectionData>
|
| 355 |
+
<PersistentDeviceSelectionData>
|
| 356 |
+
<option name="api" value="33" />
|
| 357 |
+
<option name="brand" value="samsung" />
|
| 358 |
+
<option name="codename" value="a32" />
|
| 359 |
+
<option name="id" value="a32" />
|
| 360 |
+
<option name="labId" value="google" />
|
| 361 |
+
<option name="manufacturer" value="Samsung" />
|
| 362 |
+
<option name="name" value="Galaxy A32" />
|
| 363 |
+
<option name="screenDensity" value="420" />
|
| 364 |
+
<option name="screenX" value="1080" />
|
| 365 |
+
<option name="screenY" value="2400" />
|
| 366 |
+
</PersistentDeviceSelectionData>
|
| 367 |
+
<PersistentDeviceSelectionData>
|
| 368 |
+
<option name="api" value="36" />
|
| 369 |
+
<option name="brand" value="samsung" />
|
| 370 |
+
<option name="codename" value="a34x" />
|
| 371 |
+
<option name="id" value="a34x" />
|
| 372 |
+
<option name="labId" value="google" />
|
| 373 |
+
<option name="manufacturer" value="Samsung" />
|
| 374 |
+
<option name="name" value="Galaxy A34 5G" />
|
| 375 |
+
<option name="screenDensity" value="450" />
|
| 376 |
+
<option name="screenX" value="1080" />
|
| 377 |
+
<option name="screenY" value="2340" />
|
| 378 |
+
</PersistentDeviceSelectionData>
|
| 379 |
+
<PersistentDeviceSelectionData>
|
| 380 |
+
<option name="api" value="34" />
|
| 381 |
+
<option name="brand" value="samsung" />
|
| 382 |
+
<option name="codename" value="a35x" />
|
| 383 |
+
<option name="id" value="a35x" />
|
| 384 |
+
<option name="labId" value="google" />
|
| 385 |
+
<option name="manufacturer" value="Samsung" />
|
| 386 |
+
<option name="name" value="Galaxy A35 5G" />
|
| 387 |
+
<option name="screenDensity" value="450" />
|
| 388 |
+
<option name="screenX" value="1080" />
|
| 389 |
+
<option name="screenY" value="2340" />
|
| 390 |
+
</PersistentDeviceSelectionData>
|
| 391 |
+
<PersistentDeviceSelectionData>
|
| 392 |
+
<option name="api" value="35" />
|
| 393 |
+
<option name="brand" value="samsung" />
|
| 394 |
+
<option name="codename" value="a35x" />
|
| 395 |
+
<option name="id" value="a35x" />
|
| 396 |
+
<option name="labId" value="google" />
|
| 397 |
+
<option name="manufacturer" value="Samsung" />
|
| 398 |
+
<option name="name" value="Galaxy A35 5G" />
|
| 399 |
+
<option name="screenDensity" value="450" />
|
| 400 |
+
<option name="screenX" value="1080" />
|
| 401 |
+
<option name="screenY" value="2340" />
|
| 402 |
+
</PersistentDeviceSelectionData>
|
| 403 |
+
<PersistentDeviceSelectionData>
|
| 404 |
+
<option name="api" value="36" />
|
| 405 |
+
<option name="brand" value="samsung" />
|
| 406 |
+
<option name="codename" value="a35x" />
|
| 407 |
+
<option name="id" value="a35x" />
|
| 408 |
+
<option name="labId" value="google" />
|
| 409 |
+
<option name="manufacturer" value="Samsung" />
|
| 410 |
+
<option name="name" value="Galaxy A35 5G" />
|
| 411 |
+
<option name="screenDensity" value="450" />
|
| 412 |
+
<option name="screenX" value="1080" />
|
| 413 |
+
<option name="screenY" value="2340" />
|
| 414 |
+
</PersistentDeviceSelectionData>
|
| 415 |
+
<PersistentDeviceSelectionData>
|
| 416 |
+
<option name="api" value="35" />
|
| 417 |
+
<option name="brand" value="samsung" />
|
| 418 |
+
<option name="codename" value="a36xq" />
|
| 419 |
+
<option name="id" value="a36xq" />
|
| 420 |
+
<option name="labId" value="google" />
|
| 421 |
+
<option name="manufacturer" value="Samsung" />
|
| 422 |
+
<option name="name" value="Galaxy A36 5G" />
|
| 423 |
+
<option name="screenDensity" value="450" />
|
| 424 |
+
<option name="screenX" value="1080" />
|
| 425 |
+
<option name="screenY" value="2340" />
|
| 426 |
+
</PersistentDeviceSelectionData>
|
| 427 |
+
<PersistentDeviceSelectionData>
|
| 428 |
+
<option name="api" value="36" />
|
| 429 |
+
<option name="brand" value="samsung" />
|
| 430 |
+
<option name="codename" value="a36xq" />
|
| 431 |
+
<option name="id" value="a36xq" />
|
| 432 |
+
<option name="labId" value="google" />
|
| 433 |
+
<option name="manufacturer" value="Samsung" />
|
| 434 |
+
<option name="name" value="Galaxy A36 5G" />
|
| 435 |
+
<option name="screenDensity" value="450" />
|
| 436 |
+
<option name="screenX" value="1080" />
|
| 437 |
+
<option name="screenY" value="2340" />
|
| 438 |
+
</PersistentDeviceSelectionData>
|
| 439 |
+
<PersistentDeviceSelectionData>
|
| 440 |
+
<option name="api" value="35" />
|
| 441 |
+
<option name="brand" value="samsung" />
|
| 442 |
+
<option name="codename" value="a56x" />
|
| 443 |
+
<option name="id" value="a56x" />
|
| 444 |
+
<option name="labId" value="google" />
|
| 445 |
+
<option name="manufacturer" value="Samsung" />
|
| 446 |
+
<option name="name" value="Galaxy A56 5G" />
|
| 447 |
+
<option name="screenDensity" value="450" />
|
| 448 |
+
<option name="screenX" value="1080" />
|
| 449 |
+
<option name="screenY" value="2340" />
|
| 450 |
+
</PersistentDeviceSelectionData>
|
| 451 |
+
<PersistentDeviceSelectionData>
|
| 452 |
+
<option name="api" value="34" />
|
| 453 |
+
<option name="brand" value="google" />
|
| 454 |
+
<option name="codename" value="akita" />
|
| 455 |
+
<option name="id" value="akita" />
|
| 456 |
+
<option name="labId" value="google" />
|
| 457 |
+
<option name="manufacturer" value="Google" />
|
| 458 |
+
<option name="name" value="Pixel 8a" />
|
| 459 |
+
<option name="screenDensity" value="420" />
|
| 460 |
+
<option name="screenX" value="1080" />
|
| 461 |
+
<option name="screenY" value="2400" />
|
| 462 |
+
</PersistentDeviceSelectionData>
|
| 463 |
+
<PersistentDeviceSelectionData>
|
| 464 |
+
<option name="api" value="35" />
|
| 465 |
+
<option name="brand" value="google" />
|
| 466 |
+
<option name="codename" value="akita" />
|
| 467 |
+
<option name="id" value="akita" />
|
| 468 |
+
<option name="labId" value="google" />
|
| 469 |
+
<option name="manufacturer" value="Google" />
|
| 470 |
+
<option name="name" value="Pixel 8a" />
|
| 471 |
+
<option name="screenDensity" value="420" />
|
| 472 |
+
<option name="screenX" value="1080" />
|
| 473 |
+
<option name="screenY" value="2400" />
|
| 474 |
+
</PersistentDeviceSelectionData>
|
| 475 |
+
<PersistentDeviceSelectionData>
|
| 476 |
+
<option name="api" value="34" />
|
| 477 |
+
<option name="brand" value="motorola" />
|
| 478 |
+
<option name="codename" value="arcfox" />
|
| 479 |
+
<option name="id" value="arcfox" />
|
| 480 |
+
<option name="labId" value="google" />
|
| 481 |
+
<option name="manufacturer" value="Motorola" />
|
| 482 |
+
<option name="name" value="razr plus 2024" />
|
| 483 |
+
<option name="screenDensity" value="360" />
|
| 484 |
+
<option name="screenX" value="1080" />
|
| 485 |
+
<option name="screenY" value="1272" />
|
| 486 |
+
</PersistentDeviceSelectionData>
|
| 487 |
+
<PersistentDeviceSelectionData>
|
| 488 |
+
<option name="api" value="30" />
|
| 489 |
+
<option name="brand" value="motorola" />
|
| 490 |
+
<option name="codename" value="aruba" />
|
| 491 |
+
<option name="id" value="aruba" />
|
| 492 |
+
<option name="labId" value="google" />
|
| 493 |
+
<option name="manufacturer" value="Motorola" />
|
| 494 |
+
<option name="name" value="moto e20" />
|
| 495 |
+
<option name="screenDensity" value="280" />
|
| 496 |
+
<option name="screenX" value="720" />
|
| 497 |
+
<option name="screenY" value="1600" />
|
| 498 |
+
</PersistentDeviceSelectionData>
|
| 499 |
+
<PersistentDeviceSelectionData>
|
| 500 |
+
<option name="api" value="33" />
|
| 501 |
+
<option name="brand" value="motorola" />
|
| 502 |
+
<option name="codename" value="austin" />
|
| 503 |
+
<option name="id" value="austin" />
|
| 504 |
+
<option name="labId" value="google" />
|
| 505 |
+
<option name="manufacturer" value="Motorola" />
|
| 506 |
+
<option name="name" value="moto g 5G (2022)" />
|
| 507 |
+
<option name="screenDensity" value="280" />
|
| 508 |
+
<option name="screenX" value="720" />
|
| 509 |
+
<option name="screenY" value="1600" />
|
| 510 |
+
</PersistentDeviceSelectionData>
|
| 511 |
+
<PersistentDeviceSelectionData>
|
| 512 |
+
<option name="api" value="33" />
|
| 513 |
+
<option name="brand" value="samsung" />
|
| 514 |
+
<option name="codename" value="b0q" />
|
| 515 |
+
<option name="id" value="b0q" />
|
| 516 |
+
<option name="labId" value="google" />
|
| 517 |
+
<option name="manufacturer" value="Samsung" />
|
| 518 |
+
<option name="name" value="Galaxy S22 Ultra" />
|
| 519 |
+
<option name="screenDensity" value="600" />
|
| 520 |
+
<option name="screenX" value="1440" />
|
| 521 |
+
<option name="screenY" value="3088" />
|
| 522 |
+
</PersistentDeviceSelectionData>
|
| 523 |
+
<PersistentDeviceSelectionData>
|
| 524 |
+
<option name="api" value="36" />
|
| 525 |
+
<option name="brand" value="samsung" />
|
| 526 |
+
<option name="codename" value="b0q" />
|
| 527 |
+
<option name="id" value="b0q" />
|
| 528 |
+
<option name="labId" value="google" />
|
| 529 |
+
<option name="manufacturer" value="Samsung" />
|
| 530 |
+
<option name="name" value="Galaxy S22 Ultra" />
|
| 531 |
+
<option name="screenDensity" value="600" />
|
| 532 |
+
<option name="screenX" value="1440" />
|
| 533 |
+
<option name="screenY" value="3088" />
|
| 534 |
+
</PersistentDeviceSelectionData>
|
| 535 |
+
<PersistentDeviceSelectionData>
|
| 536 |
+
<option name="api" value="36" />
|
| 537 |
+
<option name="brand" value="samsung" />
|
| 538 |
+
<option name="codename" value="b5qsqw" />
|
| 539 |
+
<option name="id" value="b5qsqw" />
|
| 540 |
+
<option name="labId" value="google" />
|
| 541 |
+
<option name="manufacturer" value="Samsung" />
|
| 542 |
+
<option name="name" value="Galaxy Z Flip5" />
|
| 543 |
+
<option name="screenDensity" value="340" />
|
| 544 |
+
<option name="screenX" value="748" />
|
| 545 |
+
<option name="screenY" value="720" />
|
| 546 |
+
</PersistentDeviceSelectionData>
|
| 547 |
+
<PersistentDeviceSelectionData>
|
| 548 |
+
<option name="api" value="34" />
|
| 549 |
+
<option name="brand" value="samsung" />
|
| 550 |
+
<option name="codename" value="b6q" />
|
| 551 |
+
<option name="id" value="b6q" />
|
| 552 |
+
<option name="labId" value="google" />
|
| 553 |
+
<option name="manufacturer" value="Samsung" />
|
| 554 |
+
<option name="name" value="Galaxy Z Flip6" />
|
| 555 |
+
<option name="screenDensity" value="340" />
|
| 556 |
+
<option name="screenX" value="1080" />
|
| 557 |
+
<option name="screenY" value="2640" />
|
| 558 |
+
</PersistentDeviceSelectionData>
|
| 559 |
+
<PersistentDeviceSelectionData>
|
| 560 |
+
<option name="api" value="36" />
|
| 561 |
+
<option name="brand" value="samsung" />
|
| 562 |
+
<option name="codename" value="b6qsqw" />
|
| 563 |
+
<option name="id" value="b6qsqw" />
|
| 564 |
+
<option name="labId" value="google" />
|
| 565 |
+
<option name="manufacturer" value="Samsung" />
|
| 566 |
+
<option name="name" value="Galaxy Z Flip6" />
|
| 567 |
+
<option name="screenDensity" value="480" />
|
| 568 |
+
<option name="screenX" value="1080" />
|
| 569 |
+
<option name="screenY" value="2640" />
|
| 570 |
+
</PersistentDeviceSelectionData>
|
| 571 |
+
<PersistentDeviceSelectionData>
|
| 572 |
+
<option name="api" value="36" />
|
| 573 |
+
<option name="brand" value="google" />
|
| 574 |
+
<option name="codename" value="blazer" />
|
| 575 |
+
<option name="default" value="true" />
|
| 576 |
+
<option name="id" value="blazer" />
|
| 577 |
+
<option name="labId" value="google" />
|
| 578 |
+
<option name="manufacturer" value="Google" />
|
| 579 |
+
<option name="name" value="Pixel 10 Pro" />
|
| 580 |
+
<option name="screenDensity" value="420" />
|
| 581 |
+
<option name="screenX" value="1080" />
|
| 582 |
+
<option name="screenY" value="2410" />
|
| 583 |
+
<option name="tags">
|
| 584 |
+
<list>
|
| 585 |
+
<option value="dda-default" />
|
| 586 |
+
</list>
|
| 587 |
+
</option>
|
| 588 |
+
</PersistentDeviceSelectionData>
|
| 589 |
+
<PersistentDeviceSelectionData>
|
| 590 |
+
<option name="api" value="32" />
|
| 591 |
+
<option name="brand" value="google" />
|
| 592 |
+
<option name="codename" value="bluejay" />
|
| 593 |
+
<option name="id" value="bluejay" />
|
| 594 |
+
<option name="labId" value="google" />
|
| 595 |
+
<option name="manufacturer" value="Google" />
|
| 596 |
+
<option name="name" value="Pixel 6a" />
|
| 597 |
+
<option name="screenDensity" value="420" />
|
| 598 |
+
<option name="screenX" value="1080" />
|
| 599 |
+
<option name="screenY" value="2400" />
|
| 600 |
+
</PersistentDeviceSelectionData>
|
| 601 |
+
<PersistentDeviceSelectionData>
|
| 602 |
+
<option name="api" value="33" />
|
| 603 |
+
<option name="brand" value="samsung" />
|
| 604 |
+
<option name="codename" value="c1q" />
|
| 605 |
+
<option name="id" value="c1q" />
|
| 606 |
+
<option name="labId" value="google" />
|
| 607 |
+
<option name="manufacturer" value="Samsung" />
|
| 608 |
+
<option name="name" value="Galaxy Note20 5G" />
|
| 609 |
+
<option name="screenDensity" value="450" />
|
| 610 |
+
<option name="screenX" value="1080" />
|
| 611 |
+
<option name="screenY" value="2400" />
|
| 612 |
+
</PersistentDeviceSelectionData>
|
| 613 |
+
<PersistentDeviceSelectionData>
|
| 614 |
+
<option name="api" value="33" />
|
| 615 |
+
<option name="brand" value="samsung" />
|
| 616 |
+
<option name="codename" value="c1qksw" />
|
| 617 |
+
<option name="id" value="c1qksw" />
|
| 618 |
+
<option name="labId" value="google" />
|
| 619 |
+
<option name="manufacturer" value="Samsung" />
|
| 620 |
+
<option name="name" value="Galaxy Note20 5G" />
|
| 621 |
+
<option name="screenDensity" value="450" />
|
| 622 |
+
<option name="screenX" value="1080" />
|
| 623 |
+
<option name="screenY" value="2400" />
|
| 624 |
+
</PersistentDeviceSelectionData>
|
| 625 |
+
<PersistentDeviceSelectionData>
|
| 626 |
+
<option name="api" value="33" />
|
| 627 |
+
<option name="brand" value="samsung" />
|
| 628 |
+
<option name="codename" value="c2q" />
|
| 629 |
+
<option name="id" value="c2q" />
|
| 630 |
+
<option name="labId" value="google" />
|
| 631 |
+
<option name="manufacturer" value="Samsung" />
|
| 632 |
+
<option name="name" value="Galaxy Note20 Ultra 5G" />
|
| 633 |
+
<option name="screenDensity" value="560" />
|
| 634 |
+
<option name="screenX" value="1440" />
|
| 635 |
+
<option name="screenY" value="3088" />
|
| 636 |
+
</PersistentDeviceSelectionData>
|
| 637 |
+
<PersistentDeviceSelectionData>
|
| 638 |
+
<option name="api" value="34" />
|
| 639 |
+
<option name="brand" value="google" />
|
| 640 |
+
<option name="codename" value="caiman" />
|
| 641 |
+
<option name="id" value="caiman" />
|
| 642 |
+
<option name="labId" value="google" />
|
| 643 |
+
<option name="manufacturer" value="Google" />
|
| 644 |
+
<option name="name" value="Pixel 9 Pro" />
|
| 645 |
+
<option name="screenDensity" value="360" />
|
| 646 |
+
<option name="screenX" value="960" />
|
| 647 |
+
<option name="screenY" value="2142" />
|
| 648 |
+
</PersistentDeviceSelectionData>
|
| 649 |
+
<PersistentDeviceSelectionData>
|
| 650 |
+
<option name="api" value="35" />
|
| 651 |
+
<option name="brand" value="google" />
|
| 652 |
+
<option name="codename" value="caiman" />
|
| 653 |
+
<option name="id" value="caiman" />
|
| 654 |
+
<option name="labId" value="google" />
|
| 655 |
+
<option name="manufacturer" value="Google" />
|
| 656 |
+
<option name="name" value="Pixel 9 Pro" />
|
| 657 |
+
<option name="screenDensity" value="360" />
|
| 658 |
+
<option name="screenX" value="960" />
|
| 659 |
+
<option name="screenY" value="2142" />
|
| 660 |
+
</PersistentDeviceSelectionData>
|
| 661 |
+
<PersistentDeviceSelectionData>
|
| 662 |
+
<option name="api" value="34" />
|
| 663 |
+
<option name="brand" value="google" />
|
| 664 |
+
<option name="codename" value="comet" />
|
| 665 |
+
<option name="id" value="comet" />
|
| 666 |
+
<option name="labId" value="google" />
|
| 667 |
+
<option name="manufacturer" value="Google" />
|
| 668 |
+
<option name="name" value="Pixel 9 Pro Fold" />
|
| 669 |
+
<option name="screenDensity" value="390" />
|
| 670 |
+
<option name="screenX" value="2076" />
|
| 671 |
+
<option name="screenY" value="2152" />
|
| 672 |
+
</PersistentDeviceSelectionData>
|
| 673 |
+
<PersistentDeviceSelectionData>
|
| 674 |
+
<option name="api" value="35" />
|
| 675 |
+
<option name="brand" value="google" />
|
| 676 |
+
<option name="codename" value="comet" />
|
| 677 |
+
<option name="id" value="comet" />
|
| 678 |
+
<option name="labId" value="google" />
|
| 679 |
+
<option name="manufacturer" value="Google" />
|
| 680 |
+
<option name="name" value="Pixel 9 Pro Fold" />
|
| 681 |
+
<option name="screenDensity" value="390" />
|
| 682 |
+
<option name="screenX" value="2076" />
|
| 683 |
+
<option name="screenY" value="2152" />
|
| 684 |
+
</PersistentDeviceSelectionData>
|
| 685 |
+
<PersistentDeviceSelectionData>
|
| 686 |
+
<option name="api" value="36" />
|
| 687 |
+
<option name="brand" value="google" />
|
| 688 |
+
<option name="codename" value="comet" />
|
| 689 |
+
<option name="id" value="comet" />
|
| 690 |
+
<option name="labId" value="google" />
|
| 691 |
+
<option name="manufacturer" value="Google" />
|
| 692 |
+
<option name="name" value="Pixel 9 Pro Fold" />
|
| 693 |
+
<option name="screenDensity" value="390" />
|
| 694 |
+
<option name="screenX" value="2076" />
|
| 695 |
+
<option name="screenY" value="2152" />
|
| 696 |
+
</PersistentDeviceSelectionData>
|
| 697 |
+
<PersistentDeviceSelectionData>
|
| 698 |
+
<option name="api" value="35" />
|
| 699 |
+
<option name="brand" value="motorola" />
|
| 700 |
+
<option name="codename" value="cuscoi" />
|
| 701 |
+
<option name="id" value="cuscoi" />
|
| 702 |
+
<option name="labId" value="google" />
|
| 703 |
+
<option name="manufacturer" value="Motorola" />
|
| 704 |
+
<option name="name" value="moto g96 5G" />
|
| 705 |
+
<option name="screenDensity" value="400" />
|
| 706 |
+
<option name="screenX" value="1080" />
|
| 707 |
+
<option name="screenY" value="2400" />
|
| 708 |
+
</PersistentDeviceSelectionData>
|
| 709 |
+
<PersistentDeviceSelectionData>
|
| 710 |
+
<option name="api" value="35" />
|
| 711 |
+
<option name="brand" value="samsung" />
|
| 712 |
+
<option name="codename" value="dm1q" />
|
| 713 |
+
<option name="id" value="dm1q" />
|
| 714 |
+
<option name="labId" value="google" />
|
| 715 |
+
<option name="manufacturer" value="Samsung" />
|
| 716 |
+
<option name="name" value="Galaxy S23" />
|
| 717 |
+
<option name="screenDensity" value="480" />
|
| 718 |
+
<option name="screenX" value="1080" />
|
| 719 |
+
<option name="screenY" value="2340" />
|
| 720 |
+
</PersistentDeviceSelectionData>
|
| 721 |
+
<PersistentDeviceSelectionData>
|
| 722 |
+
<option name="api" value="36" />
|
| 723 |
+
<option name="brand" value="samsung" />
|
| 724 |
+
<option name="codename" value="dm1q-SM-S911U" />
|
| 725 |
+
<option name="id" value="dm1q-SM-S911U" />
|
| 726 |
+
<option name="labId" value="google" />
|
| 727 |
+
<option name="manufacturer" value="Samsung" />
|
| 728 |
+
<option name="name" value="Galaxy S23" />
|
| 729 |
+
<option name="screenDensity" value="480" />
|
| 730 |
+
<option name="screenX" value="1080" />
|
| 731 |
+
<option name="screenY" value="2340" />
|
| 732 |
+
</PersistentDeviceSelectionData>
|
| 733 |
+
<PersistentDeviceSelectionData>
|
| 734 |
+
<option name="api" value="36" />
|
| 735 |
+
<option name="brand" value="samsung" />
|
| 736 |
+
<option name="codename" value="dm1qcsx" />
|
| 737 |
+
<option name="id" value="dm1qcsx" />
|
| 738 |
+
<option name="labId" value="google" />
|
| 739 |
+
<option name="manufacturer" value="Samsung" />
|
| 740 |
+
<option name="name" value="Galaxy S23" />
|
| 741 |
+
<option name="screenDensity" value="480" />
|
| 742 |
+
<option name="screenX" value="1080" />
|
| 743 |
+
<option name="screenY" value="2340" />
|
| 744 |
+
</PersistentDeviceSelectionData>
|
| 745 |
+
<PersistentDeviceSelectionData>
|
| 746 |
+
<option name="api" value="34" />
|
| 747 |
+
<option name="brand" value="samsung" />
|
| 748 |
+
<option name="codename" value="dm2q" />
|
| 749 |
+
<option name="id" value="dm2q" />
|
| 750 |
+
<option name="labId" value="google" />
|
| 751 |
+
<option name="manufacturer" value="Samsung" />
|
| 752 |
+
<option name="name" value="Galaxy S23+" />
|
| 753 |
+
<option name="screenDensity" value="450" />
|
| 754 |
+
<option name="screenX" value="1080" />
|
| 755 |
+
<option name="screenY" value="2340" />
|
| 756 |
+
</PersistentDeviceSelectionData>
|
| 757 |
+
<PersistentDeviceSelectionData>
|
| 758 |
+
<option name="api" value="34" />
|
| 759 |
+
<option name="brand" value="samsung" />
|
| 760 |
+
<option name="codename" value="dm3q" />
|
| 761 |
+
<option name="id" value="dm3q" />
|
| 762 |
+
<option name="labId" value="google" />
|
| 763 |
+
<option name="manufacturer" value="Samsung" />
|
| 764 |
+
<option name="name" value="Galaxy S23 Ultra" />
|
| 765 |
+
<option name="screenDensity" value="600" />
|
| 766 |
+
<option name="screenX" value="1440" />
|
| 767 |
+
<option name="screenY" value="3088" />
|
| 768 |
+
</PersistentDeviceSelectionData>
|
| 769 |
+
<PersistentDeviceSelectionData>
|
| 770 |
+
<option name="api" value="34" />
|
| 771 |
+
<option name="brand" value="motorola" />
|
| 772 |
+
<option name="codename" value="dubai" />
|
| 773 |
+
<option name="id" value="dubai" />
|
| 774 |
+
<option name="labId" value="google" />
|
| 775 |
+
<option name="manufacturer" value="Motorola" />
|
| 776 |
+
<option name="name" value="edge 30" />
|
| 777 |
+
<option name="screenDensity" value="405" />
|
| 778 |
+
<option name="screenX" value="1080" />
|
| 779 |
+
<option name="screenY" value="2400" />
|
| 780 |
+
</PersistentDeviceSelectionData>
|
| 781 |
+
<PersistentDeviceSelectionData>
|
| 782 |
+
<option name="api" value="34" />
|
| 783 |
+
<option name="brand" value="samsung" />
|
| 784 |
+
<option name="codename" value="e1q" />
|
| 785 |
+
<option name="id" value="e1q" />
|
| 786 |
+
<option name="labId" value="google" />
|
| 787 |
+
<option name="manufacturer" value="Samsung" />
|
| 788 |
+
<option name="name" value="Galaxy S24" />
|
| 789 |
+
<option name="screenDensity" value="480" />
|
| 790 |
+
<option name="screenX" value="1080" />
|
| 791 |
+
<option name="screenY" value="2340" />
|
| 792 |
+
</PersistentDeviceSelectionData>
|
| 793 |
+
<PersistentDeviceSelectionData>
|
| 794 |
+
<option name="api" value="36" />
|
| 795 |
+
<option name="brand" value="samsung" />
|
| 796 |
+
<option name="codename" value="e1q" />
|
| 797 |
+
<option name="id" value="e1q" />
|
| 798 |
+
<option name="labId" value="google" />
|
| 799 |
+
<option name="manufacturer" value="Samsung" />
|
| 800 |
+
<option name="name" value="Galaxy S24" />
|
| 801 |
+
<option name="screenDensity" value="480" />
|
| 802 |
+
<option name="screenX" value="1080" />
|
| 803 |
+
<option name="screenY" value="2340" />
|
| 804 |
+
</PersistentDeviceSelectionData>
|
| 805 |
+
<PersistentDeviceSelectionData>
|
| 806 |
+
<option name="api" value="36" />
|
| 807 |
+
<option name="brand" value="samsung" />
|
| 808 |
+
<option name="codename" value="e1s" />
|
| 809 |
+
<option name="id" value="e1s" />
|
| 810 |
+
<option name="labId" value="google" />
|
| 811 |
+
<option name="manufacturer" value="Samsung" />
|
| 812 |
+
<option name="name" value="Galaxy S24" />
|
| 813 |
+
<option name="screenDensity" value="480" />
|
| 814 |
+
<option name="screenX" value="1080" />
|
| 815 |
+
<option name="screenY" value="2340" />
|
| 816 |
+
</PersistentDeviceSelectionData>
|
| 817 |
+
<PersistentDeviceSelectionData>
|
| 818 |
+
<option name="api" value="36" />
|
| 819 |
+
<option name="brand" value="samsung" />
|
| 820 |
+
<option name="codename" value="e2q" />
|
| 821 |
+
<option name="id" value="e2q" />
|
| 822 |
+
<option name="labId" value="google" />
|
| 823 |
+
<option name="manufacturer" value="Samsung" />
|
| 824 |
+
<option name="name" value="Galaxy S24+" />
|
| 825 |
+
<option name="screenDensity" value="450" />
|
| 826 |
+
<option name="screenX" value="1080" />
|
| 827 |
+
<option name="screenY" value="2340" />
|
| 828 |
+
</PersistentDeviceSelectionData>
|
| 829 |
+
<PersistentDeviceSelectionData>
|
| 830 |
+
<option name="api" value="36" />
|
| 831 |
+
<option name="brand" value="samsung" />
|
| 832 |
+
<option name="codename" value="e2s" />
|
| 833 |
+
<option name="id" value="e2s" />
|
| 834 |
+
<option name="labId" value="google" />
|
| 835 |
+
<option name="manufacturer" value="Samsung" />
|
| 836 |
+
<option name="name" value="Galaxy S24+" />
|
| 837 |
+
<option name="screenDensity" value="450" />
|
| 838 |
+
<option name="screenX" value="1080" />
|
| 839 |
+
<option name="screenY" value="2340" />
|
| 840 |
+
</PersistentDeviceSelectionData>
|
| 841 |
+
<PersistentDeviceSelectionData>
|
| 842 |
+
<option name="api" value="34" />
|
| 843 |
+
<option name="brand" value="samsung" />
|
| 844 |
+
<option name="codename" value="e3q" />
|
| 845 |
+
<option name="id" value="e3q" />
|
| 846 |
+
<option name="labId" value="google" />
|
| 847 |
+
<option name="manufacturer" value="Samsung" />
|
| 848 |
+
<option name="name" value="Galaxy S24 Ultra" />
|
| 849 |
+
<option name="screenDensity" value="450" />
|
| 850 |
+
<option name="screenX" value="1440" />
|
| 851 |
+
<option name="screenY" value="3120" />
|
| 852 |
+
</PersistentDeviceSelectionData>
|
| 853 |
+
<PersistentDeviceSelectionData>
|
| 854 |
+
<option name="api" value="36" />
|
| 855 |
+
<option name="brand" value="samsung" />
|
| 856 |
+
<option name="codename" value="e3q" />
|
| 857 |
+
<option name="id" value="e3q" />
|
| 858 |
+
<option name="labId" value="google" />
|
| 859 |
+
<option name="manufacturer" value="Samsung" />
|
| 860 |
+
<option name="name" value="Galaxy S24 Ultra" />
|
| 861 |
+
<option name="screenDensity" value="450" />
|
| 862 |
+
<option name="screenX" value="1440" />
|
| 863 |
+
<option name="screenY" value="3120" />
|
| 864 |
+
</PersistentDeviceSelectionData>
|
| 865 |
+
<PersistentDeviceSelectionData>
|
| 866 |
+
<option name="api" value="36" />
|
| 867 |
+
<option name="brand" value="samsung" />
|
| 868 |
+
<option name="codename" value="e3qcsx" />
|
| 869 |
+
<option name="id" value="e3qcsx" />
|
| 870 |
+
<option name="labId" value="google" />
|
| 871 |
+
<option name="manufacturer" value="Samsung" />
|
| 872 |
+
<option name="name" value="Galaxy S24 Ultra" />
|
| 873 |
+
<option name="screenDensity" value="450" />
|
| 874 |
+
<option name="screenX" value="1440" />
|
| 875 |
+
<option name="screenY" value="3120" />
|
| 876 |
+
</PersistentDeviceSelectionData>
|
| 877 |
+
<PersistentDeviceSelectionData>
|
| 878 |
+
<option name="api" value="36" />
|
| 879 |
+
<option name="brand" value="samsung" />
|
| 880 |
+
<option name="codename" value="e3qksx" />
|
| 881 |
+
<option name="id" value="e3qksx" />
|
| 882 |
+
<option name="labId" value="google" />
|
| 883 |
+
<option name="manufacturer" value="Samsung" />
|
| 884 |
+
<option name="name" value="Galaxy S24 Ultra" />
|
| 885 |
+
<option name="screenDensity" value="450" />
|
| 886 |
+
<option name="screenX" value="1440" />
|
| 887 |
+
<option name="screenY" value="3120" />
|
| 888 |
+
</PersistentDeviceSelectionData>
|
| 889 |
+
<PersistentDeviceSelectionData>
|
| 890 |
+
<option name="api" value="33" />
|
| 891 |
+
<option name="brand" value="google" />
|
| 892 |
+
<option name="codename" value="eos" />
|
| 893 |
+
<option name="id" value="eos" />
|
| 894 |
+
<option name="labId" value="google" />
|
| 895 |
+
<option name="manufacturer" value="Google" />
|
| 896 |
+
<option name="name" value="Eos" />
|
| 897 |
+
<option name="screenDensity" value="320" />
|
| 898 |
+
<option name="screenX" value="384" />
|
| 899 |
+
<option name="screenY" value="384" />
|
| 900 |
+
</PersistentDeviceSelectionData>
|
| 901 |
+
<PersistentDeviceSelectionData>
|
| 902 |
+
<option name="api" value="33" />
|
| 903 |
+
<option name="brand" value="google" />
|
| 904 |
+
<option name="codename" value="felix" />
|
| 905 |
+
<option name="id" value="felix" />
|
| 906 |
+
<option name="labId" value="google" />
|
| 907 |
+
<option name="manufacturer" value="Google" />
|
| 908 |
+
<option name="name" value="Pixel Fold" />
|
| 909 |
+
<option name="screenDensity" value="420" />
|
| 910 |
+
<option name="screenX" value="2208" />
|
| 911 |
+
<option name="screenY" value="1840" />
|
| 912 |
+
</PersistentDeviceSelectionData>
|
| 913 |
+
<PersistentDeviceSelectionData>
|
| 914 |
+
<option name="api" value="34" />
|
| 915 |
+
<option name="brand" value="google" />
|
| 916 |
+
<option name="codename" value="felix" />
|
| 917 |
+
<option name="id" value="felix" />
|
| 918 |
+
<option name="labId" value="google" />
|
| 919 |
+
<option name="manufacturer" value="Google" />
|
| 920 |
+
<option name="name" value="Pixel Fold" />
|
| 921 |
+
<option name="screenDensity" value="420" />
|
| 922 |
+
<option name="screenX" value="2208" />
|
| 923 |
+
<option name="screenY" value="1840" />
|
| 924 |
+
</PersistentDeviceSelectionData>
|
| 925 |
+
<PersistentDeviceSelectionData>
|
| 926 |
+
<option name="api" value="36" />
|
| 927 |
+
<option name="brand" value="google" />
|
| 928 |
+
<option name="codename" value="felix" />
|
| 929 |
+
<option name="id" value="felix" />
|
| 930 |
+
<option name="labId" value="google" />
|
| 931 |
+
<option name="manufacturer" value="Google" />
|
| 932 |
+
<option name="name" value="Pixel Fold" />
|
| 933 |
+
<option name="screenDensity" value="420" />
|
| 934 |
+
<option name="screenX" value="2208" />
|
| 935 |
+
<option name="screenY" value="1840" />
|
| 936 |
+
</PersistentDeviceSelectionData>
|
| 937 |
+
<PersistentDeviceSelectionData>
|
| 938 |
+
<option name="api" value="33" />
|
| 939 |
+
<option name="brand" value="google" />
|
| 940 |
+
<option name="codename" value="felix_camera" />
|
| 941 |
+
<option name="id" value="felix_camera" />
|
| 942 |
+
<option name="labId" value="google" />
|
| 943 |
+
<option name="manufacturer" value="Google" />
|
| 944 |
+
<option name="name" value="Pixel Fold (Camera-enabled)" />
|
| 945 |
+
<option name="screenDensity" value="420" />
|
| 946 |
+
<option name="screenX" value="2208" />
|
| 947 |
+
<option name="screenY" value="1840" />
|
| 948 |
+
</PersistentDeviceSelectionData>
|
| 949 |
+
<PersistentDeviceSelectionData>
|
| 950 |
+
<option name="api" value="34" />
|
| 951 |
+
<option name="brand" value="motorola" />
|
| 952 |
+
<option name="codename" value="fogona" />
|
| 953 |
+
<option name="id" value="fogona" />
|
| 954 |
+
<option name="labId" value="google" />
|
| 955 |
+
<option name="manufacturer" value="Motorola" />
|
| 956 |
+
<option name="name" value="moto g play - 2024" />
|
| 957 |
+
<option name="screenDensity" value="280" />
|
| 958 |
+
<option name="screenX" value="720" />
|
| 959 |
+
<option name="screenY" value="1600" />
|
| 960 |
+
</PersistentDeviceSelectionData>
|
| 961 |
+
<PersistentDeviceSelectionData>
|
| 962 |
+
<option name="api" value="34" />
|
| 963 |
+
<option name="brand" value="motorola" />
|
| 964 |
+
<option name="codename" value="fogorow" />
|
| 965 |
+
<option name="id" value="fogorow" />
|
| 966 |
+
<option name="labId" value="google" />
|
| 967 |
+
<option name="manufacturer" value="Motorola" />
|
| 968 |
+
<option name="name" value="moto g24" />
|
| 969 |
+
<option name="screenDensity" value="280" />
|
| 970 |
+
<option name="screenX" value="720" />
|
| 971 |
+
<option name="screenY" value="1612" />
|
| 972 |
+
</PersistentDeviceSelectionData>
|
| 973 |
+
<PersistentDeviceSelectionData>
|
| 974 |
+
<option name="api" value="34" />
|
| 975 |
+
<option name="brand" value="motorola" />
|
| 976 |
+
<option name="codename" value="fogos" />
|
| 977 |
+
<option name="id" value="fogos" />
|
| 978 |
+
<option name="labId" value="google" />
|
| 979 |
+
<option name="manufacturer" value="Motorola" />
|
| 980 |
+
<option name="name" value="moto g34 5G" />
|
| 981 |
+
<option name="screenDensity" value="280" />
|
| 982 |
+
<option name="screenX" value="720" />
|
| 983 |
+
<option name="screenY" value="1600" />
|
| 984 |
+
</PersistentDeviceSelectionData>
|
| 985 |
+
<PersistentDeviceSelectionData>
|
| 986 |
+
<option name="api" value="35" />
|
| 987 |
+
<option name="brand" value="motorola" />
|
| 988 |
+
<option name="codename" value="fogos" />
|
| 989 |
+
<option name="id" value="fogos" />
|
| 990 |
+
<option name="labId" value="google" />
|
| 991 |
+
<option name="manufacturer" value="Motorola" />
|
| 992 |
+
<option name="name" value="moto g34 5G" />
|
| 993 |
+
<option name="screenDensity" value="280" />
|
| 994 |
+
<option name="screenX" value="720" />
|
| 995 |
+
<option name="screenY" value="1600" />
|
| 996 |
+
</PersistentDeviceSelectionData>
|
| 997 |
+
<PersistentDeviceSelectionData>
|
| 998 |
+
<option name="api" value="36" />
|
| 999 |
+
<option name="brand" value="google" />
|
| 1000 |
+
<option name="codename" value="frankel" />
|
| 1001 |
+
<option name="default" value="true" />
|
| 1002 |
+
<option name="id" value="frankel" />
|
| 1003 |
+
<option name="labId" value="google" />
|
| 1004 |
+
<option name="manufacturer" value="Google" />
|
| 1005 |
+
<option name="name" value="Pixel 10" />
|
| 1006 |
+
<option name="screenDensity" value="420" />
|
| 1007 |
+
<option name="screenX" value="1080" />
|
| 1008 |
+
<option name="screenY" value="2424" />
|
| 1009 |
+
<option name="tags">
|
| 1010 |
+
<list>
|
| 1011 |
+
<option value="dda-default" />
|
| 1012 |
+
</list>
|
| 1013 |
+
</option>
|
| 1014 |
+
</PersistentDeviceSelectionData>
|
| 1015 |
+
<PersistentDeviceSelectionData>
|
| 1016 |
+
<option name="api" value="34" />
|
| 1017 |
+
<option name="brand" value="samsung" />
|
| 1018 |
+
<option name="codename" value="g0q" />
|
| 1019 |
+
<option name="id" value="g0q" />
|
| 1020 |
+
<option name="labId" value="google" />
|
| 1021 |
+
<option name="manufacturer" value="Samsung" />
|
| 1022 |
+
<option name="name" value="Galaxy S22+" />
|
| 1023 |
+
<option name="screenDensity" value="450" />
|
| 1024 |
+
<option name="screenX" value="1080" />
|
| 1025 |
+
<option name="screenY" value="2340" />
|
| 1026 |
+
</PersistentDeviceSelectionData>
|
| 1027 |
+
<PersistentDeviceSelectionData>
|
| 1028 |
+
<option name="api" value="36" />
|
| 1029 |
+
<option name="brand" value="samsung" />
|
| 1030 |
+
<option name="codename" value="g0q" />
|
| 1031 |
+
<option name="id" value="g0q" />
|
| 1032 |
+
<option name="labId" value="google" />
|
| 1033 |
+
<option name="manufacturer" value="Samsung" />
|
| 1034 |
+
<option name="name" value="Galaxy S22+" />
|
| 1035 |
+
<option name="screenDensity" value="450" />
|
| 1036 |
+
<option name="screenX" value="1080" />
|
| 1037 |
+
<option name="screenY" value="2340" />
|
| 1038 |
+
</PersistentDeviceSelectionData>
|
| 1039 |
+
<PersistentDeviceSelectionData>
|
| 1040 |
+
<option name="api" value="33" />
|
| 1041 |
+
<option name="brand" value="motorola" />
|
| 1042 |
+
<option name="codename" value="gnevan" />
|
| 1043 |
+
<option name="id" value="gnevan" />
|
| 1044 |
+
<option name="labId" value="google" />
|
| 1045 |
+
<option name="manufacturer" value="Motorola" />
|
| 1046 |
+
<option name="name" value="moto g stylus (2023)" />
|
| 1047 |
+
<option name="screenDensity" value="280" />
|
| 1048 |
+
<option name="screenX" value="720" />
|
| 1049 |
+
<option name="screenY" value="1600" />
|
| 1050 |
+
</PersistentDeviceSelectionData>
|
| 1051 |
+
<PersistentDeviceSelectionData>
|
| 1052 |
+
<option name="api" value="34" />
|
| 1053 |
+
<option name="brand" value="samsung" />
|
| 1054 |
+
<option name="codename" value="gta9pwifi" />
|
| 1055 |
+
<option name="id" value="gta9pwifi" />
|
| 1056 |
+
<option name="labId" value="google" />
|
| 1057 |
+
<option name="manufacturer" value="Samsung" />
|
| 1058 |
+
<option name="name" value="Galaxy Tab A9+" />
|
| 1059 |
+
<option name="screenDensity" value="240" />
|
| 1060 |
+
<option name="screenX" value="1200" />
|
| 1061 |
+
<option name="screenY" value="1920" />
|
| 1062 |
+
</PersistentDeviceSelectionData>
|
| 1063 |
+
<PersistentDeviceSelectionData>
|
| 1064 |
+
<option name="api" value="33" />
|
| 1065 |
+
<option name="brand" value="samsung" />
|
| 1066 |
+
<option name="codename" value="gts7lwifi" />
|
| 1067 |
+
<option name="id" value="gts7lwifi" />
|
| 1068 |
+
<option name="labId" value="google" />
|
| 1069 |
+
<option name="manufacturer" value="Samsung" />
|
| 1070 |
+
<option name="name" value="Galaxy Tab S7" />
|
| 1071 |
+
<option name="screenDensity" value="340" />
|
| 1072 |
+
<option name="screenX" value="1600" />
|
| 1073 |
+
<option name="screenY" value="2560" />
|
| 1074 |
+
</PersistentDeviceSelectionData>
|
| 1075 |
+
<PersistentDeviceSelectionData>
|
| 1076 |
+
<option name="api" value="34" />
|
| 1077 |
+
<option name="brand" value="samsung" />
|
| 1078 |
+
<option name="codename" value="gts7xllite" />
|
| 1079 |
+
<option name="id" value="gts7xllite" />
|
| 1080 |
+
<option name="labId" value="google" />
|
| 1081 |
+
<option name="manufacturer" value="Samsung" />
|
| 1082 |
+
<option name="name" value="Galaxy Tab S7 FE 5G" />
|
| 1083 |
+
<option name="screenDensity" value="340" />
|
| 1084 |
+
<option name="screenX" value="1600" />
|
| 1085 |
+
<option name="screenY" value="2560" />
|
| 1086 |
+
</PersistentDeviceSelectionData>
|
| 1087 |
+
<PersistentDeviceSelectionData>
|
| 1088 |
+
<option name="api" value="33" />
|
| 1089 |
+
<option name="brand" value="samsung" />
|
| 1090 |
+
<option name="codename" value="gts8uwifi" />
|
| 1091 |
+
<option name="formFactor" value="Tablet" />
|
| 1092 |
+
<option name="id" value="gts8uwifi" />
|
| 1093 |
+
<option name="labId" value="google" />
|
| 1094 |
+
<option name="manufacturer" value="Samsung" />
|
| 1095 |
+
<option name="name" value="Galaxy Tab S8 Ultra" />
|
| 1096 |
+
<option name="screenDensity" value="320" />
|
| 1097 |
+
<option name="screenX" value="1848" />
|
| 1098 |
+
<option name="screenY" value="2960" />
|
| 1099 |
+
</PersistentDeviceSelectionData>
|
| 1100 |
+
<PersistentDeviceSelectionData>
|
| 1101 |
+
<option name="api" value="34" />
|
| 1102 |
+
<option name="brand" value="samsung" />
|
| 1103 |
+
<option name="codename" value="gts8wifi" />
|
| 1104 |
+
<option name="formFactor" value="Tablet" />
|
| 1105 |
+
<option name="id" value="gts8wifi" />
|
| 1106 |
+
<option name="labId" value="google" />
|
| 1107 |
+
<option name="manufacturer" value="Samsung" />
|
| 1108 |
+
<option name="name" value="Galaxy Tab S8" />
|
| 1109 |
+
<option name="screenDensity" value="274" />
|
| 1110 |
+
<option name="screenX" value="1600" />
|
| 1111 |
+
<option name="screenY" value="2560" />
|
| 1112 |
+
</PersistentDeviceSelectionData>
|
| 1113 |
+
<PersistentDeviceSelectionData>
|
| 1114 |
+
<option name="api" value="34" />
|
| 1115 |
+
<option name="brand" value="samsung" />
|
| 1116 |
+
<option name="codename" value="gts9fe" />
|
| 1117 |
+
<option name="id" value="gts9fe" />
|
| 1118 |
+
<option name="labId" value="google" />
|
| 1119 |
+
<option name="manufacturer" value="Samsung" />
|
| 1120 |
+
<option name="name" value="Galaxy Tab S9 FE 5G" />
|
| 1121 |
+
<option name="screenDensity" value="280" />
|
| 1122 |
+
<option name="screenX" value="1440" />
|
| 1123 |
+
<option name="screenY" value="2304" />
|
| 1124 |
+
</PersistentDeviceSelectionData>
|
| 1125 |
+
<PersistentDeviceSelectionData>
|
| 1126 |
+
<option name="api" value="34" />
|
| 1127 |
+
<option name="brand" value="samsung" />
|
| 1128 |
+
<option name="codename" value="gts9wifi" />
|
| 1129 |
+
<option name="id" value="gts9wifi" />
|
| 1130 |
+
<option name="labId" value="google" />
|
| 1131 |
+
<option name="manufacturer" value="Samsung" />
|
| 1132 |
+
<option name="name" value="Galaxy Tab S9" />
|
| 1133 |
+
<option name="screenDensity" value="340" />
|
| 1134 |
+
<option name="screenX" value="1600" />
|
| 1135 |
+
<option name="screenY" value="2560" />
|
| 1136 |
+
</PersistentDeviceSelectionData>
|
| 1137 |
+
<PersistentDeviceSelectionData>
|
| 1138 |
+
<option name="api" value="30" />
|
| 1139 |
+
<option name="brand" value="motorola" />
|
| 1140 |
+
<option name="codename" value="guamna" />
|
| 1141 |
+
<option name="id" value="guamna" />
|
| 1142 |
+
<option name="labId" value="google" />
|
| 1143 |
+
<option name="manufacturer" value="Motorola" />
|
| 1144 |
+
<option name="name" value="moto g play (2021)" />
|
| 1145 |
+
<option name="screenDensity" value="280" />
|
| 1146 |
+
<option name="screenX" value="720" />
|
| 1147 |
+
<option name="screenY" value="1600" />
|
| 1148 |
+
</PersistentDeviceSelectionData>
|
| 1149 |
+
<PersistentDeviceSelectionData>
|
| 1150 |
+
<option name="api" value="30" />
|
| 1151 |
+
<option name="brand" value="motorola" />
|
| 1152 |
+
<option name="codename" value="guamp" />
|
| 1153 |
+
<option name="id" value="guamp" />
|
| 1154 |
+
<option name="labId" value="google" />
|
| 1155 |
+
<option name="manufacturer" value="Motorola" />
|
| 1156 |
+
<option name="name" value="moto g(9) play" />
|
| 1157 |
+
<option name="screenDensity" value="280" />
|
| 1158 |
+
<option name="screenX" value="720" />
|
| 1159 |
+
<option name="screenY" value="1600" />
|
| 1160 |
+
</PersistentDeviceSelectionData>
|
| 1161 |
+
<PersistentDeviceSelectionData>
|
| 1162 |
+
<option name="api" value="34" />
|
| 1163 |
+
<option name="brand" value="google" />
|
| 1164 |
+
<option name="codename" value="husky" />
|
| 1165 |
+
<option name="id" value="husky" />
|
| 1166 |
+
<option name="labId" value="google" />
|
| 1167 |
+
<option name="manufacturer" value="Google" />
|
| 1168 |
+
<option name="name" value="Pixel 8 Pro" />
|
| 1169 |
+
<option name="screenDensity" value="390" />
|
| 1170 |
+
<option name="screenX" value="1008" />
|
| 1171 |
+
<option name="screenY" value="2244" />
|
| 1172 |
+
</PersistentDeviceSelectionData>
|
| 1173 |
+
<PersistentDeviceSelectionData>
|
| 1174 |
+
<option name="api" value="35" />
|
| 1175 |
+
<option name="brand" value="google" />
|
| 1176 |
+
<option name="codename" value="husky" />
|
| 1177 |
+
<option name="id" value="husky" />
|
| 1178 |
+
<option name="labId" value="google" />
|
| 1179 |
+
<option name="manufacturer" value="Google" />
|
| 1180 |
+
<option name="name" value="Pixel 8 Pro" />
|
| 1181 |
+
<option name="screenDensity" value="390" />
|
| 1182 |
+
<option name="screenX" value="1008" />
|
| 1183 |
+
<option name="screenY" value="2244" />
|
| 1184 |
+
</PersistentDeviceSelectionData>
|
| 1185 |
+
<PersistentDeviceSelectionData>
|
| 1186 |
+
<option name="api" value="30" />
|
| 1187 |
+
<option name="brand" value="motorola" />
|
| 1188 |
+
<option name="codename" value="java" />
|
| 1189 |
+
<option name="id" value="java" />
|
| 1190 |
+
<option name="labId" value="google" />
|
| 1191 |
+
<option name="manufacturer" value="Motorola" />
|
| 1192 |
+
<option name="name" value="G20" />
|
| 1193 |
+
<option name="screenDensity" value="280" />
|
| 1194 |
+
<option name="screenX" value="720" />
|
| 1195 |
+
<option name="screenY" value="1600" />
|
| 1196 |
+
</PersistentDeviceSelectionData>
|
| 1197 |
+
<PersistentDeviceSelectionData>
|
| 1198 |
+
<option name="api" value="35" />
|
| 1199 |
+
<option name="brand" value="motorola" />
|
| 1200 |
+
<option name="codename" value="kansas" />
|
| 1201 |
+
<option name="id" value="kansas" />
|
| 1202 |
+
<option name="labId" value="google" />
|
| 1203 |
+
<option name="manufacturer" value="Motorola" />
|
| 1204 |
+
<option name="name" value="moto g - 2025" />
|
| 1205 |
+
<option name="screenDensity" value="280" />
|
| 1206 |
+
<option name="screenX" value="720" />
|
| 1207 |
+
<option name="screenY" value="1604" />
|
| 1208 |
+
</PersistentDeviceSelectionData>
|
| 1209 |
+
<PersistentDeviceSelectionData>
|
| 1210 |
+
<option name="api" value="34" />
|
| 1211 |
+
<option name="brand" value="google" />
|
| 1212 |
+
<option name="codename" value="komodo" />
|
| 1213 |
+
<option name="id" value="komodo" />
|
| 1214 |
+
<option name="labId" value="google" />
|
| 1215 |
+
<option name="manufacturer" value="Google" />
|
| 1216 |
+
<option name="name" value="Pixel 9 Pro XL" />
|
| 1217 |
+
<option name="screenDensity" value="360" />
|
| 1218 |
+
<option name="screenX" value="1008" />
|
| 1219 |
+
<option name="screenY" value="2244" />
|
| 1220 |
+
</PersistentDeviceSelectionData>
|
| 1221 |
+
<PersistentDeviceSelectionData>
|
| 1222 |
+
<option name="api" value="35" />
|
| 1223 |
+
<option name="brand" value="google" />
|
| 1224 |
+
<option name="codename" value="komodo" />
|
| 1225 |
+
<option name="id" value="komodo" />
|
| 1226 |
+
<option name="labId" value="google" />
|
| 1227 |
+
<option name="manufacturer" value="Google" />
|
| 1228 |
+
<option name="name" value="Pixel 9 Pro XL" />
|
| 1229 |
+
<option name="screenDensity" value="360" />
|
| 1230 |
+
<option name="screenX" value="1008" />
|
| 1231 |
+
<option name="screenY" value="2244" />
|
| 1232 |
+
</PersistentDeviceSelectionData>
|
| 1233 |
+
<PersistentDeviceSelectionData>
|
| 1234 |
+
<option name="api" value="35" />
|
| 1235 |
+
<option name="brand" value="motorola" />
|
| 1236 |
+
<option name="codename" value="lamul" />
|
| 1237 |
+
<option name="id" value="lamul" />
|
| 1238 |
+
<option name="labId" value="google" />
|
| 1239 |
+
<option name="manufacturer" value="Motorola" />
|
| 1240 |
+
<option name="name" value="moto g05" />
|
| 1241 |
+
<option name="screenDensity" value="280" />
|
| 1242 |
+
<option name="screenX" value="720" />
|
| 1243 |
+
<option name="screenY" value="1604" />
|
| 1244 |
+
</PersistentDeviceSelectionData>
|
| 1245 |
+
<PersistentDeviceSelectionData>
|
| 1246 |
+
<option name="api" value="34" />
|
| 1247 |
+
<option name="brand" value="motorola" />
|
| 1248 |
+
<option name="codename" value="lion" />
|
| 1249 |
+
<option name="id" value="lion" />
|
| 1250 |
+
<option name="labId" value="google" />
|
| 1251 |
+
<option name="manufacturer" value="Motorola" />
|
| 1252 |
+
<option name="name" value="moto g04" />
|
| 1253 |
+
<option name="screenDensity" value="280" />
|
| 1254 |
+
<option name="screenX" value="720" />
|
| 1255 |
+
<option name="screenY" value="1612" />
|
| 1256 |
+
</PersistentDeviceSelectionData>
|
| 1257 |
+
<PersistentDeviceSelectionData>
|
| 1258 |
+
<option name="api" value="33" />
|
| 1259 |
+
<option name="brand" value="google" />
|
| 1260 |
+
<option name="codename" value="lynx" />
|
| 1261 |
+
<option name="id" value="lynx" />
|
| 1262 |
+
<option name="labId" value="google" />
|
| 1263 |
+
<option name="manufacturer" value="Google" />
|
| 1264 |
+
<option name="name" value="Pixel 7a" />
|
| 1265 |
+
<option name="screenDensity" value="420" />
|
| 1266 |
+
<option name="screenX" value="1080" />
|
| 1267 |
+
<option name="screenY" value="2400" />
|
| 1268 |
+
</PersistentDeviceSelectionData>
|
| 1269 |
+
<PersistentDeviceSelectionData>
|
| 1270 |
+
<option name="api" value="34" />
|
| 1271 |
+
<option name="brand" value="motorola" />
|
| 1272 |
+
<option name="codename" value="lyriq" />
|
| 1273 |
+
<option name="id" value="lyriq" />
|
| 1274 |
+
<option name="labId" value="google" />
|
| 1275 |
+
<option name="manufacturer" value="Motorola" />
|
| 1276 |
+
<option name="name" value="edge 40" />
|
| 1277 |
+
<option name="screenDensity" value="400" />
|
| 1278 |
+
<option name="screenX" value="1080" />
|
| 1279 |
+
<option name="screenY" value="2400" />
|
| 1280 |
+
</PersistentDeviceSelectionData>
|
| 1281 |
+
<PersistentDeviceSelectionData>
|
| 1282 |
+
<option name="api" value="36" />
|
| 1283 |
+
<option name="brand" value="samsung" />
|
| 1284 |
+
<option name="codename" value="m1q" />
|
| 1285 |
+
<option name="id" value="m1q" />
|
| 1286 |
+
<option name="labId" value="google" />
|
| 1287 |
+
<option name="manufacturer" value="Samsung" />
|
| 1288 |
+
<option name="name" value="Galaxy S26" />
|
| 1289 |
+
<option name="screenDensity" value="480" />
|
| 1290 |
+
<option name="screenX" value="1080" />
|
| 1291 |
+
<option name="screenY" value="2340" />
|
| 1292 |
+
</PersistentDeviceSelectionData>
|
| 1293 |
+
<PersistentDeviceSelectionData>
|
| 1294 |
+
<option name="api" value="36" />
|
| 1295 |
+
<option name="brand" value="samsung" />
|
| 1296 |
+
<option name="codename" value="m2q" />
|
| 1297 |
+
<option name="id" value="m2q" />
|
| 1298 |
+
<option name="labId" value="google" />
|
| 1299 |
+
<option name="manufacturer" value="Samsung" />
|
| 1300 |
+
<option name="name" value="Galaxy S26+" />
|
| 1301 |
+
<option name="screenDensity" value="450" />
|
| 1302 |
+
<option name="screenX" value="1080" />
|
| 1303 |
+
<option name="screenY" value="2340" />
|
| 1304 |
+
</PersistentDeviceSelectionData>
|
| 1305 |
+
<PersistentDeviceSelectionData>
|
| 1306 |
+
<option name="api" value="34" />
|
| 1307 |
+
<option name="brand" value="motorola" />
|
| 1308 |
+
<option name="codename" value="manaus" />
|
| 1309 |
+
<option name="id" value="manaus" />
|
| 1310 |
+
<option name="labId" value="google" />
|
| 1311 |
+
<option name="manufacturer" value="Motorola" />
|
| 1312 |
+
<option name="name" value="edge 40 neo" />
|
| 1313 |
+
<option name="screenDensity" value="400" />
|
| 1314 |
+
<option name="screenX" value="1080" />
|
| 1315 |
+
<option name="screenY" value="2400" />
|
| 1316 |
+
</PersistentDeviceSelectionData>
|
| 1317 |
+
<PersistentDeviceSelectionData>
|
| 1318 |
+
<option name="api" value="33" />
|
| 1319 |
+
<option name="brand" value="motorola" />
|
| 1320 |
+
<option name="codename" value="maui" />
|
| 1321 |
+
<option name="id" value="maui" />
|
| 1322 |
+
<option name="labId" value="google" />
|
| 1323 |
+
<option name="manufacturer" value="Motorola" />
|
| 1324 |
+
<option name="name" value="moto g play - 2023" />
|
| 1325 |
+
<option name="screenDensity" value="280" />
|
| 1326 |
+
<option name="screenX" value="720" />
|
| 1327 |
+
<option name="screenY" value="1600" />
|
| 1328 |
+
</PersistentDeviceSelectionData>
|
| 1329 |
+
<PersistentDeviceSelectionData>
|
| 1330 |
+
<option name="api" value="36" />
|
| 1331 |
+
<option name="brand" value="google" />
|
| 1332 |
+
<option name="codename" value="mustang" />
|
| 1333 |
+
<option name="default" value="true" />
|
| 1334 |
+
<option name="id" value="mustang" />
|
| 1335 |
+
<option name="labId" value="google" />
|
| 1336 |
+
<option name="manufacturer" value="Google" />
|
| 1337 |
+
<option name="name" value="Pixel 10 Pro XL" />
|
| 1338 |
+
<option name="screenDensity" value="390" />
|
| 1339 |
+
<option name="screenX" value="1080" />
|
| 1340 |
+
<option name="screenY" value="2404" />
|
| 1341 |
+
<option name="tags">
|
| 1342 |
+
<list>
|
| 1343 |
+
<option value="dda-default" />
|
| 1344 |
+
</list>
|
| 1345 |
+
</option>
|
| 1346 |
+
</PersistentDeviceSelectionData>
|
| 1347 |
+
<PersistentDeviceSelectionData>
|
| 1348 |
+
<option name="api" value="34" />
|
| 1349 |
+
<option name="brand" value="samsung" />
|
| 1350 |
+
<option name="codename" value="o1q" />
|
| 1351 |
+
<option name="id" value="o1q" />
|
| 1352 |
+
<option name="labId" value="google" />
|
| 1353 |
+
<option name="manufacturer" value="Samsung" />
|
| 1354 |
+
<option name="name" value="Galaxy S21 5G" />
|
| 1355 |
+
<option name="screenDensity" value="421" />
|
| 1356 |
+
<option name="screenX" value="1080" />
|
| 1357 |
+
<option name="screenY" value="2400" />
|
| 1358 |
+
</PersistentDeviceSelectionData>
|
| 1359 |
+
<PersistentDeviceSelectionData>
|
| 1360 |
+
<option name="api" value="35" />
|
| 1361 |
+
<option name="brand" value="samsung" />
|
| 1362 |
+
<option name="codename" value="o1q" />
|
| 1363 |
+
<option name="id" value="o1q" />
|
| 1364 |
+
<option name="labId" value="google" />
|
| 1365 |
+
<option name="manufacturer" value="Samsung" />
|
| 1366 |
+
<option name="name" value="Galaxy S21 5G" />
|
| 1367 |
+
<option name="screenDensity" value="421" />
|
| 1368 |
+
<option name="screenX" value="1080" />
|
| 1369 |
+
<option name="screenY" value="2400" />
|
| 1370 |
+
</PersistentDeviceSelectionData>
|
| 1371 |
+
<PersistentDeviceSelectionData>
|
| 1372 |
+
<option name="api" value="31" />
|
| 1373 |
+
<option name="brand" value="google" />
|
| 1374 |
+
<option name="codename" value="oriole" />
|
| 1375 |
+
<option name="id" value="oriole" />
|
| 1376 |
+
<option name="labId" value="google" />
|
| 1377 |
+
<option name="manufacturer" value="Google" />
|
| 1378 |
+
<option name="name" value="Pixel 6" />
|
| 1379 |
+
<option name="screenDensity" value="420" />
|
| 1380 |
+
<option name="screenX" value="1080" />
|
| 1381 |
+
<option name="screenY" value="2400" />
|
| 1382 |
+
</PersistentDeviceSelectionData>
|
| 1383 |
+
<PersistentDeviceSelectionData>
|
| 1384 |
+
<option name="api" value="35" />
|
| 1385 |
+
<option name="brand" value="samsung" />
|
| 1386 |
+
<option name="codename" value="p3q" />
|
| 1387 |
+
<option name="id" value="p3q" />
|
| 1388 |
+
<option name="labId" value="google" />
|
| 1389 |
+
<option name="manufacturer" value="Samsung" />
|
| 1390 |
+
<option name="name" value="Galaxy S21 Ultra 5G" />
|
| 1391 |
+
<option name="screenDensity" value="600" />
|
| 1392 |
+
<option name="screenX" value="1440" />
|
| 1393 |
+
<option name="screenY" value="3200" />
|
| 1394 |
+
</PersistentDeviceSelectionData>
|
| 1395 |
+
<PersistentDeviceSelectionData>
|
| 1396 |
+
<option name="api" value="35" />
|
| 1397 |
+
<option name="brand" value="samsung" />
|
| 1398 |
+
<option name="codename" value="p3s" />
|
| 1399 |
+
<option name="id" value="p3s" />
|
| 1400 |
+
<option name="labId" value="google" />
|
| 1401 |
+
<option name="manufacturer" value="Samsung" />
|
| 1402 |
+
<option name="name" value="Galaxy S21 Ultra 5G" />
|
| 1403 |
+
<option name="screenDensity" value="600" />
|
| 1404 |
+
<option name="screenX" value="1440" />
|
| 1405 |
+
<option name="screenY" value="3200" />
|
| 1406 |
+
</PersistentDeviceSelectionData>
|
| 1407 |
+
<PersistentDeviceSelectionData>
|
| 1408 |
+
<option name="api" value="36" />
|
| 1409 |
+
<option name="brand" value="samsung" />
|
| 1410 |
+
<option name="codename" value="pa2q" />
|
| 1411 |
+
<option name="id" value="pa2q" />
|
| 1412 |
+
<option name="labId" value="google" />
|
| 1413 |
+
<option name="manufacturer" value="Samsung" />
|
| 1414 |
+
<option name="name" value="Galaxy S25+" />
|
| 1415 |
+
<option name="screenDensity" value="450" />
|
| 1416 |
+
<option name="screenX" value="1080" />
|
| 1417 |
+
<option name="screenY" value="2340" />
|
| 1418 |
+
</PersistentDeviceSelectionData>
|
| 1419 |
+
<PersistentDeviceSelectionData>
|
| 1420 |
+
<option name="api" value="36" />
|
| 1421 |
+
<option name="brand" value="samsung" />
|
| 1422 |
+
<option name="codename" value="pa2qxxx" />
|
| 1423 |
+
<option name="id" value="pa2qxxx" />
|
| 1424 |
+
<option name="labId" value="google" />
|
| 1425 |
+
<option name="manufacturer" value="Samsung" />
|
| 1426 |
+
<option name="name" value="Galaxy S25+" />
|
| 1427 |
+
<option name="screenDensity" value="450" />
|
| 1428 |
+
<option name="screenX" value="1080" />
|
| 1429 |
+
<option name="screenY" value="2340" />
|
| 1430 |
+
</PersistentDeviceSelectionData>
|
| 1431 |
+
<PersistentDeviceSelectionData>
|
| 1432 |
+
<option name="api" value="35" />
|
| 1433 |
+
<option name="brand" value="samsung" />
|
| 1434 |
+
<option name="codename" value="pa3q" />
|
| 1435 |
+
<option name="default" value="true" />
|
| 1436 |
+
<option name="id" value="pa3q" />
|
| 1437 |
+
<option name="labId" value="google" />
|
| 1438 |
+
<option name="manufacturer" value="Samsung" />
|
| 1439 |
+
<option name="name" value="Galaxy S25 Ultra" />
|
| 1440 |
+
<option name="screenDensity" value="450" />
|
| 1441 |
+
<option name="screenX" value="1080" />
|
| 1442 |
+
<option name="screenY" value="2340" />
|
| 1443 |
+
<option name="tags">
|
| 1444 |
+
<list>
|
| 1445 |
+
<option value="dda-default" />
|
| 1446 |
+
</list>
|
| 1447 |
+
</option>
|
| 1448 |
+
</PersistentDeviceSelectionData>
|
| 1449 |
+
<PersistentDeviceSelectionData>
|
| 1450 |
+
<option name="api" value="36" />
|
| 1451 |
+
<option name="brand" value="samsung" />
|
| 1452 |
+
<option name="codename" value="pa3q" />
|
| 1453 |
+
<option name="default" value="true" />
|
| 1454 |
+
<option name="id" value="pa3q" />
|
| 1455 |
+
<option name="labId" value="google" />
|
| 1456 |
+
<option name="manufacturer" value="Samsung" />
|
| 1457 |
+
<option name="name" value="Galaxy S25 Ultra" />
|
| 1458 |
+
<option name="screenDensity" value="450" />
|
| 1459 |
+
<option name="screenX" value="1080" />
|
| 1460 |
+
<option name="screenY" value="2340" />
|
| 1461 |
+
<option name="tags">
|
| 1462 |
+
<list>
|
| 1463 |
+
<option value="dda-default" />
|
| 1464 |
+
</list>
|
| 1465 |
+
</option>
|
| 1466 |
+
</PersistentDeviceSelectionData>
|
| 1467 |
+
<PersistentDeviceSelectionData>
|
| 1468 |
+
<option name="api" value="33" />
|
| 1469 |
+
<option name="brand" value="google" />
|
| 1470 |
+
<option name="codename" value="panther" />
|
| 1471 |
+
<option name="id" value="panther" />
|
| 1472 |
+
<option name="labId" value="google" />
|
| 1473 |
+
<option name="manufacturer" value="Google" />
|
| 1474 |
+
<option name="name" value="Pixel 7" />
|
| 1475 |
+
<option name="screenDensity" value="420" />
|
| 1476 |
+
<option name="screenX" value="1080" />
|
| 1477 |
+
<option name="screenY" value="2400" />
|
| 1478 |
+
</PersistentDeviceSelectionData>
|
| 1479 |
+
<PersistentDeviceSelectionData>
|
| 1480 |
+
<option name="api" value="35" />
|
| 1481 |
+
<option name="brand" value="samsung" />
|
| 1482 |
+
<option name="codename" value="psq" />
|
| 1483 |
+
<option name="id" value="psq" />
|
| 1484 |
+
<option name="labId" value="google" />
|
| 1485 |
+
<option name="manufacturer" value="Samsung" />
|
| 1486 |
+
<option name="name" value="Galaxy S25 Edge" />
|
| 1487 |
+
<option name="screenDensity" value="450" />
|
| 1488 |
+
<option name="screenX" value="1440" />
|
| 1489 |
+
<option name="screenY" value="3120" />
|
| 1490 |
+
</PersistentDeviceSelectionData>
|
| 1491 |
+
<PersistentDeviceSelectionData>
|
| 1492 |
+
<option name="api" value="36" />
|
| 1493 |
+
<option name="brand" value="samsung" />
|
| 1494 |
+
<option name="codename" value="q4qksx" />
|
| 1495 |
+
<option name="id" value="q4qksx" />
|
| 1496 |
+
<option name="labId" value="google" />
|
| 1497 |
+
<option name="manufacturer" value="Samsung" />
|
| 1498 |
+
<option name="name" value="Galaxy Z Fold4" />
|
| 1499 |
+
<option name="screenDensity" value="420" />
|
| 1500 |
+
<option name="screenX" value="1812" />
|
| 1501 |
+
<option name="screenY" value="2176" />
|
| 1502 |
+
</PersistentDeviceSelectionData>
|
| 1503 |
+
<PersistentDeviceSelectionData>
|
| 1504 |
+
<option name="api" value="34" />
|
| 1505 |
+
<option name="brand" value="samsung" />
|
| 1506 |
+
<option name="codename" value="q5q" />
|
| 1507 |
+
<option name="id" value="q5q" />
|
| 1508 |
+
<option name="labId" value="google" />
|
| 1509 |
+
<option name="manufacturer" value="Samsung" />
|
| 1510 |
+
<option name="name" value="Galaxy Z Fold5" />
|
| 1511 |
+
<option name="screenDensity" value="420" />
|
| 1512 |
+
<option name="screenX" value="1812" />
|
| 1513 |
+
<option name="screenY" value="2176" />
|
| 1514 |
+
</PersistentDeviceSelectionData>
|
| 1515 |
+
<PersistentDeviceSelectionData>
|
| 1516 |
+
<option name="api" value="36" />
|
| 1517 |
+
<option name="brand" value="samsung" />
|
| 1518 |
+
<option name="codename" value="q5qksx" />
|
| 1519 |
+
<option name="id" value="q5qksx" />
|
| 1520 |
+
<option name="labId" value="google" />
|
| 1521 |
+
<option name="manufacturer" value="Samsung" />
|
| 1522 |
+
<option name="name" value="Galaxy Z Fold5" />
|
| 1523 |
+
<option name="screenDensity" value="420" />
|
| 1524 |
+
<option name="screenX" value="1812" />
|
| 1525 |
+
<option name="screenY" value="2176" />
|
| 1526 |
+
</PersistentDeviceSelectionData>
|
| 1527 |
+
<PersistentDeviceSelectionData>
|
| 1528 |
+
<option name="api" value="34" />
|
| 1529 |
+
<option name="brand" value="samsung" />
|
| 1530 |
+
<option name="codename" value="q6q" />
|
| 1531 |
+
<option name="id" value="q6q" />
|
| 1532 |
+
<option name="labId" value="google" />
|
| 1533 |
+
<option name="manufacturer" value="Samsung" />
|
| 1534 |
+
<option name="name" value="Galaxy Z Fold6" />
|
| 1535 |
+
<option name="screenDensity" value="420" />
|
| 1536 |
+
<option name="screenX" value="1856" />
|
| 1537 |
+
<option name="screenY" value="2160" />
|
| 1538 |
+
</PersistentDeviceSelectionData>
|
| 1539 |
+
<PersistentDeviceSelectionData>
|
| 1540 |
+
<option name="api" value="36" />
|
| 1541 |
+
<option name="brand" value="samsung" />
|
| 1542 |
+
<option name="codename" value="q7mq" />
|
| 1543 |
+
<option name="id" value="q7mq" />
|
| 1544 |
+
<option name="labId" value="google" />
|
| 1545 |
+
<option name="manufacturer" value="Samsung" />
|
| 1546 |
+
<option name="name" value="Galaxy Z TriFold" />
|
| 1547 |
+
<option name="screenDensity" value="320" />
|
| 1548 |
+
<option name="screenX" value="2160" />
|
| 1549 |
+
<option name="screenY" value="1584" />
|
| 1550 |
+
</PersistentDeviceSelectionData>
|
| 1551 |
+
<PersistentDeviceSelectionData>
|
| 1552 |
+
<option name="api" value="36" />
|
| 1553 |
+
<option name="brand" value="samsung" />
|
| 1554 |
+
<option name="codename" value="r0q" />
|
| 1555 |
+
<option name="id" value="r0q" />
|
| 1556 |
+
<option name="labId" value="google" />
|
| 1557 |
+
<option name="manufacturer" value="Samsung" />
|
| 1558 |
+
<option name="name" value="Galaxy S22" />
|
| 1559 |
+
<option name="screenDensity" value="425" />
|
| 1560 |
+
<option name="screenX" value="1080" />
|
| 1561 |
+
<option name="screenY" value="2340" />
|
| 1562 |
+
</PersistentDeviceSelectionData>
|
| 1563 |
+
<PersistentDeviceSelectionData>
|
| 1564 |
+
<option name="api" value="36" />
|
| 1565 |
+
<option name="brand" value="samsung" />
|
| 1566 |
+
<option name="codename" value="r0qcsx" />
|
| 1567 |
+
<option name="id" value="r0qcsx" />
|
| 1568 |
+
<option name="labId" value="google" />
|
| 1569 |
+
<option name="manufacturer" value="Samsung" />
|
| 1570 |
+
<option name="name" value="Galaxy S22" />
|
| 1571 |
+
<option name="screenDensity" value="480" />
|
| 1572 |
+
<option name="screenX" value="1080" />
|
| 1573 |
+
<option name="screenY" value="2340" />
|
| 1574 |
+
</PersistentDeviceSelectionData>
|
| 1575 |
+
<PersistentDeviceSelectionData>
|
| 1576 |
+
<option name="api" value="30" />
|
| 1577 |
+
<option name="brand" value="google" />
|
| 1578 |
+
<option name="codename" value="r11" />
|
| 1579 |
+
<option name="formFactor" value="Wear OS" />
|
| 1580 |
+
<option name="id" value="r11" />
|
| 1581 |
+
<option name="labId" value="google" />
|
| 1582 |
+
<option name="manufacturer" value="Google" />
|
| 1583 |
+
<option name="name" value="Pixel Watch" />
|
| 1584 |
+
<option name="screenDensity" value="320" />
|
| 1585 |
+
<option name="screenX" value="384" />
|
| 1586 |
+
<option name="screenY" value="384" />
|
| 1587 |
+
<option name="type" value="WEAR_OS" />
|
| 1588 |
+
</PersistentDeviceSelectionData>
|
| 1589 |
+
<PersistentDeviceSelectionData>
|
| 1590 |
+
<option name="api" value="34" />
|
| 1591 |
+
<option name="brand" value="samsung" />
|
| 1592 |
+
<option name="codename" value="r11q" />
|
| 1593 |
+
<option name="id" value="r11q" />
|
| 1594 |
+
<option name="labId" value="google" />
|
| 1595 |
+
<option name="manufacturer" value="Samsung" />
|
| 1596 |
+
<option name="name" value="Galaxy S23 FE" />
|
| 1597 |
+
<option name="screenDensity" value="450" />
|
| 1598 |
+
<option name="screenX" value="1080" />
|
| 1599 |
+
<option name="screenY" value="2340" />
|
| 1600 |
+
</PersistentDeviceSelectionData>
|
| 1601 |
+
<PersistentDeviceSelectionData>
|
| 1602 |
+
<option name="api" value="36" />
|
| 1603 |
+
<option name="brand" value="samsung" />
|
| 1604 |
+
<option name="codename" value="r11s" />
|
| 1605 |
+
<option name="id" value="r11s" />
|
| 1606 |
+
<option name="labId" value="google" />
|
| 1607 |
+
<option name="manufacturer" value="Samsung" />
|
| 1608 |
+
<option name="name" value="Galaxy S23 FE" />
|
| 1609 |
+
<option name="screenDensity" value="450" />
|
| 1610 |
+
<option name="screenX" value="1080" />
|
| 1611 |
+
<option name="screenY" value="2340" />
|
| 1612 |
+
</PersistentDeviceSelectionData>
|
| 1613 |
+
<PersistentDeviceSelectionData>
|
| 1614 |
+
<option name="api" value="33" />
|
| 1615 |
+
<option name="brand" value="samsung" />
|
| 1616 |
+
<option name="codename" value="r8q" />
|
| 1617 |
+
<option name="id" value="r8q" />
|
| 1618 |
+
<option name="labId" value="google" />
|
| 1619 |
+
<option name="manufacturer" value="Samsung" />
|
| 1620 |
+
<option name="name" value="Galaxy S20 FE 5G" />
|
| 1621 |
+
<option name="screenDensity" value="480" />
|
| 1622 |
+
<option name="screenX" value="1080" />
|
| 1623 |
+
<option name="screenY" value="2400" />
|
| 1624 |
+
</PersistentDeviceSelectionData>
|
| 1625 |
+
<PersistentDeviceSelectionData>
|
| 1626 |
+
<option name="api" value="33" />
|
| 1627 |
+
<option name="brand" value="samsung" />
|
| 1628 |
+
<option name="codename" value="r8qksx" />
|
| 1629 |
+
<option name="id" value="r8qksx" />
|
| 1630 |
+
<option name="labId" value="google" />
|
| 1631 |
+
<option name="manufacturer" value="Samsung" />
|
| 1632 |
+
<option name="name" value="Galaxy S20 FE 5G" />
|
| 1633 |
+
<option name="screenDensity" value="480" />
|
| 1634 |
+
<option name="screenX" value="1080" />
|
| 1635 |
+
<option name="screenY" value="2400" />
|
| 1636 |
+
</PersistentDeviceSelectionData>
|
| 1637 |
+
<PersistentDeviceSelectionData>
|
| 1638 |
+
<option name="api" value="36" />
|
| 1639 |
+
<option name="brand" value="samsung" />
|
| 1640 |
+
<option name="codename" value="r9q" />
|
| 1641 |
+
<option name="id" value="r9q" />
|
| 1642 |
+
<option name="labId" value="google" />
|
| 1643 |
+
<option name="manufacturer" value="Samsung" />
|
| 1644 |
+
<option name="name" value="Galaxy S21 FE 5G" />
|
| 1645 |
+
<option name="screenDensity" value="480" />
|
| 1646 |
+
<option name="screenX" value="1080" />
|
| 1647 |
+
<option name="screenY" value="2340" />
|
| 1648 |
+
</PersistentDeviceSelectionData>
|
| 1649 |
+
<PersistentDeviceSelectionData>
|
| 1650 |
+
<option name="api" value="36" />
|
| 1651 |
+
<option name="brand" value="samsung" />
|
| 1652 |
+
<option name="codename" value="r9q-SM-G990U" />
|
| 1653 |
+
<option name="id" value="r9q-SM-G990U" />
|
| 1654 |
+
<option name="labId" value="google" />
|
| 1655 |
+
<option name="manufacturer" value="Samsung" />
|
| 1656 |
+
<option name="name" value="Galaxy S21 FE 5G" />
|
| 1657 |
+
<option name="screenDensity" value="480" />
|
| 1658 |
+
<option name="screenX" value="1080" />
|
| 1659 |
+
<option name="screenY" value="2340" />
|
| 1660 |
+
</PersistentDeviceSelectionData>
|
| 1661 |
+
<PersistentDeviceSelectionData>
|
| 1662 |
+
<option name="api" value="36" />
|
| 1663 |
+
<option name="brand" value="google" />
|
| 1664 |
+
<option name="codename" value="rango" />
|
| 1665 |
+
<option name="default" value="true" />
|
| 1666 |
+
<option name="id" value="rango" />
|
| 1667 |
+
<option name="labId" value="google" />
|
| 1668 |
+
<option name="manufacturer" value="Google" />
|
| 1669 |
+
<option name="name" value="Pixel 10 Pro Fold" />
|
| 1670 |
+
<option name="screenDensity" value="390" />
|
| 1671 |
+
<option name="screenX" value="2076" />
|
| 1672 |
+
<option name="screenY" value="2152" />
|
| 1673 |
+
<option name="tags">
|
| 1674 |
+
<list>
|
| 1675 |
+
<option value="dda-default" />
|
| 1676 |
+
</list>
|
| 1677 |
+
</option>
|
| 1678 |
+
</PersistentDeviceSelectionData>
|
| 1679 |
+
<PersistentDeviceSelectionData>
|
| 1680 |
+
<option name="api" value="30" />
|
| 1681 |
+
<option name="brand" value="google" />
|
| 1682 |
+
<option name="codename" value="redfin" />
|
| 1683 |
+
<option name="id" value="redfin" />
|
| 1684 |
+
<option name="labId" value="google" />
|
| 1685 |
+
<option name="manufacturer" value="Google" />
|
| 1686 |
+
<option name="name" value="Pixel 5" />
|
| 1687 |
+
<option name="screenDensity" value="440" />
|
| 1688 |
+
<option name="screenX" value="1080" />
|
| 1689 |
+
<option name="screenY" value="2340" />
|
| 1690 |
+
<option name="tags">
|
| 1691 |
+
<list>
|
| 1692 |
+
<option value="default" />
|
| 1693 |
+
</list>
|
| 1694 |
+
</option>
|
| 1695 |
+
</PersistentDeviceSelectionData>
|
| 1696 |
+
<PersistentDeviceSelectionData>
|
| 1697 |
+
<option name="api" value="34" />
|
| 1698 |
+
<option name="brand" value="google" />
|
| 1699 |
+
<option name="codename" value="shiba" />
|
| 1700 |
+
<option name="id" value="shiba" />
|
| 1701 |
+
<option name="labId" value="google" />
|
| 1702 |
+
<option name="manufacturer" value="Google" />
|
| 1703 |
+
<option name="name" value="Pixel 8" />
|
| 1704 |
+
<option name="screenDensity" value="420" />
|
| 1705 |
+
<option name="screenX" value="1080" />
|
| 1706 |
+
<option name="screenY" value="2400" />
|
| 1707 |
+
</PersistentDeviceSelectionData>
|
| 1708 |
+
<PersistentDeviceSelectionData>
|
| 1709 |
+
<option name="api" value="36" />
|
| 1710 |
+
<option name="brand" value="google" />
|
| 1711 |
+
<option name="codename" value="shiba" />
|
| 1712 |
+
<option name="id" value="shiba" />
|
| 1713 |
+
<option name="labId" value="google" />
|
| 1714 |
+
<option name="manufacturer" value="Google" />
|
| 1715 |
+
<option name="name" value="Pixel 8" />
|
| 1716 |
+
<option name="screenDensity" value="420" />
|
| 1717 |
+
<option name="screenX" value="1080" />
|
| 1718 |
+
<option name="screenY" value="2400" />
|
| 1719 |
+
</PersistentDeviceSelectionData>
|
| 1720 |
+
<PersistentDeviceSelectionData>
|
| 1721 |
+
<option name="api" value="34" />
|
| 1722 |
+
<option name="brand" value="samsung" />
|
| 1723 |
+
<option name="codename" value="t2q" />
|
| 1724 |
+
<option name="id" value="t2q" />
|
| 1725 |
+
<option name="labId" value="google" />
|
| 1726 |
+
<option name="manufacturer" value="Samsung" />
|
| 1727 |
+
<option name="name" value="Galaxy S21+ 5G" />
|
| 1728 |
+
<option name="screenDensity" value="394" />
|
| 1729 |
+
<option name="screenX" value="1080" />
|
| 1730 |
+
<option name="screenY" value="2400" />
|
| 1731 |
+
</PersistentDeviceSelectionData>
|
| 1732 |
+
<PersistentDeviceSelectionData>
|
| 1733 |
+
<option name="api" value="35" />
|
| 1734 |
+
<option name="brand" value="samsung" />
|
| 1735 |
+
<option name="codename" value="t2q" />
|
| 1736 |
+
<option name="id" value="t2q" />
|
| 1737 |
+
<option name="labId" value="google" />
|
| 1738 |
+
<option name="manufacturer" value="Samsung" />
|
| 1739 |
+
<option name="name" value="Galaxy S21+ 5G" />
|
| 1740 |
+
<option name="screenDensity" value="394" />
|
| 1741 |
+
<option name="screenX" value="1080" />
|
| 1742 |
+
<option name="screenY" value="2400" />
|
| 1743 |
+
</PersistentDeviceSelectionData>
|
| 1744 |
+
<PersistentDeviceSelectionData>
|
| 1745 |
+
<option name="api" value="33" />
|
| 1746 |
+
<option name="brand" value="google" />
|
| 1747 |
+
<option name="codename" value="tangorpro" />
|
| 1748 |
+
<option name="formFactor" value="Tablet" />
|
| 1749 |
+
<option name="id" value="tangorpro" />
|
| 1750 |
+
<option name="labId" value="google" />
|
| 1751 |
+
<option name="manufacturer" value="Google" />
|
| 1752 |
+
<option name="name" value="Pixel Tablet" />
|
| 1753 |
+
<option name="screenDensity" value="320" />
|
| 1754 |
+
<option name="screenX" value="1600" />
|
| 1755 |
+
<option name="screenY" value="2560" />
|
| 1756 |
+
</PersistentDeviceSelectionData>
|
| 1757 |
+
<PersistentDeviceSelectionData>
|
| 1758 |
+
<option name="api" value="36" />
|
| 1759 |
+
<option name="brand" value="google" />
|
| 1760 |
+
<option name="codename" value="tangorpro" />
|
| 1761 |
+
<option name="formFactor" value="Tablet" />
|
| 1762 |
+
<option name="id" value="tangorpro" />
|
| 1763 |
+
<option name="labId" value="google" />
|
| 1764 |
+
<option name="manufacturer" value="Google" />
|
| 1765 |
+
<option name="name" value="Pixel Tablet" />
|
| 1766 |
+
<option name="screenDensity" value="320" />
|
| 1767 |
+
<option name="screenX" value="1600" />
|
| 1768 |
+
<option name="screenY" value="2560" />
|
| 1769 |
+
</PersistentDeviceSelectionData>
|
| 1770 |
+
<PersistentDeviceSelectionData>
|
| 1771 |
+
<option name="api" value="35" />
|
| 1772 |
+
<option name="brand" value="google" />
|
| 1773 |
+
<option name="codename" value="tegu" />
|
| 1774 |
+
<option name="id" value="tegu" />
|
| 1775 |
+
<option name="labId" value="google" />
|
| 1776 |
+
<option name="manufacturer" value="Google" />
|
| 1777 |
+
<option name="name" value="Pixel 9a" />
|
| 1778 |
+
<option name="screenDensity" value="420" />
|
| 1779 |
+
<option name="screenX" value="1080" />
|
| 1780 |
+
<option name="screenY" value="2424" />
|
| 1781 |
+
</PersistentDeviceSelectionData>
|
| 1782 |
+
<PersistentDeviceSelectionData>
|
| 1783 |
+
<option name="api" value="34" />
|
| 1784 |
+
<option name="brand" value="google" />
|
| 1785 |
+
<option name="codename" value="tokay" />
|
| 1786 |
+
<option name="id" value="tokay" />
|
| 1787 |
+
<option name="labId" value="google" />
|
| 1788 |
+
<option name="manufacturer" value="Google" />
|
| 1789 |
+
<option name="name" value="Pixel 9" />
|
| 1790 |
+
<option name="screenDensity" value="420" />
|
| 1791 |
+
<option name="screenX" value="1080" />
|
| 1792 |
+
<option name="screenY" value="2424" />
|
| 1793 |
+
</PersistentDeviceSelectionData>
|
| 1794 |
+
<PersistentDeviceSelectionData>
|
| 1795 |
+
<option name="api" value="35" />
|
| 1796 |
+
<option name="brand" value="google" />
|
| 1797 |
+
<option name="codename" value="tokay" />
|
| 1798 |
+
<option name="id" value="tokay" />
|
| 1799 |
+
<option name="labId" value="google" />
|
| 1800 |
+
<option name="manufacturer" value="Google" />
|
| 1801 |
+
<option name="name" value="Pixel 9" />
|
| 1802 |
+
<option name="screenDensity" value="420" />
|
| 1803 |
+
<option name="screenX" value="1080" />
|
| 1804 |
+
<option name="screenY" value="2424" />
|
| 1805 |
+
</PersistentDeviceSelectionData>
|
| 1806 |
+
<PersistentDeviceSelectionData>
|
| 1807 |
+
<option name="api" value="36" />
|
| 1808 |
+
<option name="brand" value="google" />
|
| 1809 |
+
<option name="codename" value="tokay" />
|
| 1810 |
+
<option name="id" value="tokay" />
|
| 1811 |
+
<option name="labId" value="google" />
|
| 1812 |
+
<option name="manufacturer" value="Google" />
|
| 1813 |
+
<option name="name" value="Pixel 9" />
|
| 1814 |
+
<option name="screenDensity" value="420" />
|
| 1815 |
+
<option name="screenX" value="1080" />
|
| 1816 |
+
<option name="screenY" value="2424" />
|
| 1817 |
+
</PersistentDeviceSelectionData>
|
| 1818 |
+
<PersistentDeviceSelectionData>
|
| 1819 |
+
<option name="api" value="34" />
|
| 1820 |
+
<option name="brand" value="samsung" />
|
| 1821 |
+
<option name="codename" value="xcover7" />
|
| 1822 |
+
<option name="id" value="xcover7" />
|
| 1823 |
+
<option name="labId" value="google" />
|
| 1824 |
+
<option name="manufacturer" value="Samsung" />
|
| 1825 |
+
<option name="name" value="Galaxy XCover7" />
|
| 1826 |
+
<option name="screenDensity" value="450" />
|
| 1827 |
+
<option name="screenX" value="1080" />
|
| 1828 |
+
<option name="screenY" value="2408" />
|
| 1829 |
+
</PersistentDeviceSelectionData>
|
| 1830 |
+
<PersistentDeviceSelectionData>
|
| 1831 |
+
<option name="api" value="33" />
|
| 1832 |
+
<option name="brand" value="samsung" />
|
| 1833 |
+
<option name="codename" value="y2q" />
|
| 1834 |
+
<option name="id" value="y2q" />
|
| 1835 |
+
<option name="labId" value="google" />
|
| 1836 |
+
<option name="manufacturer" value="Samsung" />
|
| 1837 |
+
<option name="name" value="Galaxy S20+ 5G" />
|
| 1838 |
+
<option name="screenDensity" value="600" />
|
| 1839 |
+
<option name="screenX" value="1440" />
|
| 1840 |
+
<option name="screenY" value="3200" />
|
| 1841 |
+
</PersistentDeviceSelectionData>
|
| 1842 |
+
<PersistentDeviceSelectionData>
|
| 1843 |
+
<option name="api" value="33" />
|
| 1844 |
+
<option name="brand" value="samsung" />
|
| 1845 |
+
<option name="codename" value="z3q" />
|
| 1846 |
+
<option name="id" value="z3q" />
|
| 1847 |
+
<option name="labId" value="google" />
|
| 1848 |
+
<option name="manufacturer" value="Samsung" />
|
| 1849 |
+
<option name="name" value="Galaxy S20 Ultra 5G" />
|
| 1850 |
+
<option name="screenDensity" value="560" />
|
| 1851 |
+
<option name="screenX" value="1440" />
|
| 1852 |
+
<option name="screenY" value="3200" />
|
| 1853 |
+
</PersistentDeviceSelectionData>
|
| 1854 |
+
</list>
|
| 1855 |
+
</option>
|
| 1856 |
+
</component>
|
| 1857 |
+
</project>
|
.idea/deviceManager.xml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="DeviceTable">
|
| 4 |
+
<option name="columnSorters">
|
| 5 |
+
<list>
|
| 6 |
+
<ColumnSorterState>
|
| 7 |
+
<option name="column" value="Name" />
|
| 8 |
+
<option name="order" value="ASCENDING" />
|
| 9 |
+
</ColumnSorterState>
|
| 10 |
+
</list>
|
| 11 |
+
</option>
|
| 12 |
+
</component>
|
| 13 |
+
</project>
|
.idea/language-learner.iml
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<module type="JAVA_MODULE" version="4">
|
| 3 |
+
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
| 4 |
+
<exclude-output />
|
| 5 |
+
<content url="file://$MODULE_DIR$">
|
| 6 |
+
<excludeFolder url="file://$MODULE_DIR$/mobile/.dart_tool" />
|
| 7 |
+
<excludeFolder url="file://$MODULE_DIR$/mobile/.pub" />
|
| 8 |
+
<excludeFolder url="file://$MODULE_DIR$/mobile/build" />
|
| 9 |
+
</content>
|
| 10 |
+
<orderEntry type="inheritedJdk" />
|
| 11 |
+
<orderEntry type="sourceFolder" forTests="false" />
|
| 12 |
+
<orderEntry type="library" name="Dart SDK" level="project" />
|
| 13 |
+
<orderEntry type="library" name="Dart Packages" level="project" />
|
| 14 |
+
</component>
|
| 15 |
+
</module>
|
.idea/libraries/Dart_Packages.xml
ADDED
|
@@ -0,0 +1,972 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="libraryTable">
|
| 2 |
+
<library name="Dart Packages" type="DartPackagesLibraryType">
|
| 3 |
+
<properties>
|
| 4 |
+
<option name="packageNameToDirsMap">
|
| 5 |
+
<entry key="archive">
|
| 6 |
+
<value>
|
| 7 |
+
<list>
|
| 8 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.9/lib" />
|
| 9 |
+
</list>
|
| 10 |
+
</value>
|
| 11 |
+
</entry>
|
| 12 |
+
<entry key="args">
|
| 13 |
+
<value>
|
| 14 |
+
<list>
|
| 15 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/lib" />
|
| 16 |
+
</list>
|
| 17 |
+
</value>
|
| 18 |
+
</entry>
|
| 19 |
+
<entry key="async">
|
| 20 |
+
<value>
|
| 21 |
+
<list>
|
| 22 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.1/lib" />
|
| 23 |
+
</list>
|
| 24 |
+
</value>
|
| 25 |
+
</entry>
|
| 26 |
+
<entry key="boolean_selector">
|
| 27 |
+
<value>
|
| 28 |
+
<list>
|
| 29 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2/lib" />
|
| 30 |
+
</list>
|
| 31 |
+
</value>
|
| 32 |
+
</entry>
|
| 33 |
+
<entry key="characters">
|
| 34 |
+
<value>
|
| 35 |
+
<list>
|
| 36 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.1/lib" />
|
| 37 |
+
</list>
|
| 38 |
+
</value>
|
| 39 |
+
</entry>
|
| 40 |
+
<entry key="checked_yaml">
|
| 41 |
+
<value>
|
| 42 |
+
<list>
|
| 43 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/checked_yaml-2.0.4/lib" />
|
| 44 |
+
</list>
|
| 45 |
+
</value>
|
| 46 |
+
</entry>
|
| 47 |
+
<entry key="cli_util">
|
| 48 |
+
<value>
|
| 49 |
+
<list>
|
| 50 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cli_util-0.4.2/lib" />
|
| 51 |
+
</list>
|
| 52 |
+
</value>
|
| 53 |
+
</entry>
|
| 54 |
+
<entry key="clock">
|
| 55 |
+
<value>
|
| 56 |
+
<list>
|
| 57 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2/lib" />
|
| 58 |
+
</list>
|
| 59 |
+
</value>
|
| 60 |
+
</entry>
|
| 61 |
+
<entry key="code_assets">
|
| 62 |
+
<value>
|
| 63 |
+
<list>
|
| 64 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/code_assets-1.0.0/lib" />
|
| 65 |
+
</list>
|
| 66 |
+
</value>
|
| 67 |
+
</entry>
|
| 68 |
+
<entry key="collection">
|
| 69 |
+
<value>
|
| 70 |
+
<list>
|
| 71 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1/lib" />
|
| 72 |
+
</list>
|
| 73 |
+
</value>
|
| 74 |
+
</entry>
|
| 75 |
+
<entry key="convert">
|
| 76 |
+
<value>
|
| 77 |
+
<list>
|
| 78 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/convert-3.1.2/lib" />
|
| 79 |
+
</list>
|
| 80 |
+
</value>
|
| 81 |
+
</entry>
|
| 82 |
+
<entry key="country_flags">
|
| 83 |
+
<value>
|
| 84 |
+
<list>
|
| 85 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/country_flags-4.1.2/lib" />
|
| 86 |
+
</list>
|
| 87 |
+
</value>
|
| 88 |
+
</entry>
|
| 89 |
+
<entry key="cross_file">
|
| 90 |
+
<value>
|
| 91 |
+
<list>
|
| 92 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cross_file-0.3.5+2/lib" />
|
| 93 |
+
</list>
|
| 94 |
+
</value>
|
| 95 |
+
</entry>
|
| 96 |
+
<entry key="crypto">
|
| 97 |
+
<value>
|
| 98 |
+
<list>
|
| 99 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/crypto-3.0.7/lib" />
|
| 100 |
+
</list>
|
| 101 |
+
</value>
|
| 102 |
+
</entry>
|
| 103 |
+
<entry key="csslib">
|
| 104 |
+
<value>
|
| 105 |
+
<list>
|
| 106 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/csslib-1.0.2/lib" />
|
| 107 |
+
</list>
|
| 108 |
+
</value>
|
| 109 |
+
</entry>
|
| 110 |
+
<entry key="cupertino_icons">
|
| 111 |
+
<value>
|
| 112 |
+
<list>
|
| 113 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cupertino_icons-1.0.9/lib" />
|
| 114 |
+
</list>
|
| 115 |
+
</value>
|
| 116 |
+
</entry>
|
| 117 |
+
<entry key="desktop_webview_window">
|
| 118 |
+
<value>
|
| 119 |
+
<list>
|
| 120 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/desktop_webview_window-0.2.3/lib" />
|
| 121 |
+
</list>
|
| 122 |
+
</value>
|
| 123 |
+
</entry>
|
| 124 |
+
<entry key="dio">
|
| 125 |
+
<value>
|
| 126 |
+
<list>
|
| 127 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio-5.9.2/lib" />
|
| 128 |
+
</list>
|
| 129 |
+
</value>
|
| 130 |
+
</entry>
|
| 131 |
+
<entry key="dio_web_adapter">
|
| 132 |
+
<value>
|
| 133 |
+
<list>
|
| 134 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio_web_adapter-2.1.2/lib" />
|
| 135 |
+
</list>
|
| 136 |
+
</value>
|
| 137 |
+
</entry>
|
| 138 |
+
<entry key="fake_async">
|
| 139 |
+
<value>
|
| 140 |
+
<list>
|
| 141 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3/lib" />
|
| 142 |
+
</list>
|
| 143 |
+
</value>
|
| 144 |
+
</entry>
|
| 145 |
+
<entry key="ffi">
|
| 146 |
+
<value>
|
| 147 |
+
<list>
|
| 148 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/ffi-2.2.0/lib" />
|
| 149 |
+
</list>
|
| 150 |
+
</value>
|
| 151 |
+
</entry>
|
| 152 |
+
<entry key="file">
|
| 153 |
+
<value>
|
| 154 |
+
<list>
|
| 155 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file-7.0.1/lib" />
|
| 156 |
+
</list>
|
| 157 |
+
</value>
|
| 158 |
+
</entry>
|
| 159 |
+
<entry key="file_selector_linux">
|
| 160 |
+
<value>
|
| 161 |
+
<list>
|
| 162 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_linux-0.9.4/lib" />
|
| 163 |
+
</list>
|
| 164 |
+
</value>
|
| 165 |
+
</entry>
|
| 166 |
+
<entry key="file_selector_macos">
|
| 167 |
+
<value>
|
| 168 |
+
<list>
|
| 169 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_macos-0.9.5/lib" />
|
| 170 |
+
</list>
|
| 171 |
+
</value>
|
| 172 |
+
</entry>
|
| 173 |
+
<entry key="file_selector_platform_interface">
|
| 174 |
+
<value>
|
| 175 |
+
<list>
|
| 176 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_platform_interface-2.7.0/lib" />
|
| 177 |
+
</list>
|
| 178 |
+
</value>
|
| 179 |
+
</entry>
|
| 180 |
+
<entry key="file_selector_windows">
|
| 181 |
+
<value>
|
| 182 |
+
<list>
|
| 183 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_windows-0.9.3+5/lib" />
|
| 184 |
+
</list>
|
| 185 |
+
</value>
|
| 186 |
+
</entry>
|
| 187 |
+
<entry key="flutter">
|
| 188 |
+
<value>
|
| 189 |
+
<list>
|
| 190 |
+
<option value="$PROJECT_DIR$/../flutter/packages/flutter/lib" />
|
| 191 |
+
</list>
|
| 192 |
+
</value>
|
| 193 |
+
</entry>
|
| 194 |
+
<entry key="flutter_launcher_icons">
|
| 195 |
+
<value>
|
| 196 |
+
<list>
|
| 197 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_launcher_icons-0.14.4/lib" />
|
| 198 |
+
</list>
|
| 199 |
+
</value>
|
| 200 |
+
</entry>
|
| 201 |
+
<entry key="flutter_lints">
|
| 202 |
+
<value>
|
| 203 |
+
<list>
|
| 204 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0/lib" />
|
| 205 |
+
</list>
|
| 206 |
+
</value>
|
| 207 |
+
</entry>
|
| 208 |
+
<entry key="flutter_plugin_android_lifecycle">
|
| 209 |
+
<value>
|
| 210 |
+
<list>
|
| 211 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.34/lib" />
|
| 212 |
+
</list>
|
| 213 |
+
</value>
|
| 214 |
+
</entry>
|
| 215 |
+
<entry key="flutter_riverpod">
|
| 216 |
+
<value>
|
| 217 |
+
<list>
|
| 218 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_riverpod-2.6.1/lib" />
|
| 219 |
+
</list>
|
| 220 |
+
</value>
|
| 221 |
+
</entry>
|
| 222 |
+
<entry key="flutter_secure_storage">
|
| 223 |
+
<value>
|
| 224 |
+
<list>
|
| 225 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage-9.2.4/lib" />
|
| 226 |
+
</list>
|
| 227 |
+
</value>
|
| 228 |
+
</entry>
|
| 229 |
+
<entry key="flutter_secure_storage_linux">
|
| 230 |
+
<value>
|
| 231 |
+
<list>
|
| 232 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/lib" />
|
| 233 |
+
</list>
|
| 234 |
+
</value>
|
| 235 |
+
</entry>
|
| 236 |
+
<entry key="flutter_secure_storage_macos">
|
| 237 |
+
<value>
|
| 238 |
+
<list>
|
| 239 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/lib" />
|
| 240 |
+
</list>
|
| 241 |
+
</value>
|
| 242 |
+
</entry>
|
| 243 |
+
<entry key="flutter_secure_storage_platform_interface">
|
| 244 |
+
<value>
|
| 245 |
+
<list>
|
| 246 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_platform_interface-1.1.2/lib" />
|
| 247 |
+
</list>
|
| 248 |
+
</value>
|
| 249 |
+
</entry>
|
| 250 |
+
<entry key="flutter_secure_storage_web">
|
| 251 |
+
<value>
|
| 252 |
+
<list>
|
| 253 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/lib" />
|
| 254 |
+
</list>
|
| 255 |
+
</value>
|
| 256 |
+
</entry>
|
| 257 |
+
<entry key="flutter_secure_storage_windows">
|
| 258 |
+
<value>
|
| 259 |
+
<list>
|
| 260 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/lib" />
|
| 261 |
+
</list>
|
| 262 |
+
</value>
|
| 263 |
+
</entry>
|
| 264 |
+
<entry key="flutter_test">
|
| 265 |
+
<value>
|
| 266 |
+
<list>
|
| 267 |
+
<option value="$PROJECT_DIR$/../flutter/packages/flutter_test/lib" />
|
| 268 |
+
</list>
|
| 269 |
+
</value>
|
| 270 |
+
</entry>
|
| 271 |
+
<entry key="flutter_tts">
|
| 272 |
+
<value>
|
| 273 |
+
<list>
|
| 274 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_tts-4.2.5/lib" />
|
| 275 |
+
</list>
|
| 276 |
+
</value>
|
| 277 |
+
</entry>
|
| 278 |
+
<entry key="flutter_web_auth_2">
|
| 279 |
+
<value>
|
| 280 |
+
<list>
|
| 281 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_web_auth_2-4.1.0/lib" />
|
| 282 |
+
</list>
|
| 283 |
+
</value>
|
| 284 |
+
</entry>
|
| 285 |
+
<entry key="flutter_web_auth_2_platform_interface">
|
| 286 |
+
<value>
|
| 287 |
+
<list>
|
| 288 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_web_auth_2_platform_interface-4.1.0/lib" />
|
| 289 |
+
</list>
|
| 290 |
+
</value>
|
| 291 |
+
</entry>
|
| 292 |
+
<entry key="flutter_web_plugins">
|
| 293 |
+
<value>
|
| 294 |
+
<list>
|
| 295 |
+
<option value="$PROJECT_DIR$/../flutter/packages/flutter_web_plugins/lib" />
|
| 296 |
+
</list>
|
| 297 |
+
</value>
|
| 298 |
+
</entry>
|
| 299 |
+
<entry key="glob">
|
| 300 |
+
<value>
|
| 301 |
+
<list>
|
| 302 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/glob-2.1.3/lib" />
|
| 303 |
+
</list>
|
| 304 |
+
</value>
|
| 305 |
+
</entry>
|
| 306 |
+
<entry key="go_router">
|
| 307 |
+
<value>
|
| 308 |
+
<list>
|
| 309 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.8.1/lib" />
|
| 310 |
+
</list>
|
| 311 |
+
</value>
|
| 312 |
+
</entry>
|
| 313 |
+
<entry key="google_fonts">
|
| 314 |
+
<value>
|
| 315 |
+
<list>
|
| 316 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_fonts-6.3.3/lib" />
|
| 317 |
+
</list>
|
| 318 |
+
</value>
|
| 319 |
+
</entry>
|
| 320 |
+
<entry key="hooks">
|
| 321 |
+
<value>
|
| 322 |
+
<list>
|
| 323 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/hooks-1.0.3/lib" />
|
| 324 |
+
</list>
|
| 325 |
+
</value>
|
| 326 |
+
</entry>
|
| 327 |
+
<entry key="html">
|
| 328 |
+
<value>
|
| 329 |
+
<list>
|
| 330 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/html-0.15.6/lib" />
|
| 331 |
+
</list>
|
| 332 |
+
</value>
|
| 333 |
+
</entry>
|
| 334 |
+
<entry key="http">
|
| 335 |
+
<value>
|
| 336 |
+
<list>
|
| 337 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http-1.6.0/lib" />
|
| 338 |
+
</list>
|
| 339 |
+
</value>
|
| 340 |
+
</entry>
|
| 341 |
+
<entry key="http_parser">
|
| 342 |
+
<value>
|
| 343 |
+
<list>
|
| 344 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.1.2/lib" />
|
| 345 |
+
</list>
|
| 346 |
+
</value>
|
| 347 |
+
</entry>
|
| 348 |
+
<entry key="image">
|
| 349 |
+
<value>
|
| 350 |
+
<list>
|
| 351 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image-4.8.0/lib" />
|
| 352 |
+
</list>
|
| 353 |
+
</value>
|
| 354 |
+
</entry>
|
| 355 |
+
<entry key="image_picker">
|
| 356 |
+
<value>
|
| 357 |
+
<list>
|
| 358 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker-1.2.1/lib" />
|
| 359 |
+
</list>
|
| 360 |
+
</value>
|
| 361 |
+
</entry>
|
| 362 |
+
<entry key="image_picker_android">
|
| 363 |
+
<value>
|
| 364 |
+
<list>
|
| 365 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_android-0.8.13+16/lib" />
|
| 366 |
+
</list>
|
| 367 |
+
</value>
|
| 368 |
+
</entry>
|
| 369 |
+
<entry key="image_picker_for_web">
|
| 370 |
+
<value>
|
| 371 |
+
<list>
|
| 372 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_for_web-3.1.1/lib" />
|
| 373 |
+
</list>
|
| 374 |
+
</value>
|
| 375 |
+
</entry>
|
| 376 |
+
<entry key="image_picker_ios">
|
| 377 |
+
<value>
|
| 378 |
+
<list>
|
| 379 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_ios-0.8.13+6/lib" />
|
| 380 |
+
</list>
|
| 381 |
+
</value>
|
| 382 |
+
</entry>
|
| 383 |
+
<entry key="image_picker_linux">
|
| 384 |
+
<value>
|
| 385 |
+
<list>
|
| 386 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_linux-0.2.2/lib" />
|
| 387 |
+
</list>
|
| 388 |
+
</value>
|
| 389 |
+
</entry>
|
| 390 |
+
<entry key="image_picker_macos">
|
| 391 |
+
<value>
|
| 392 |
+
<list>
|
| 393 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_macos-0.2.2+1/lib" />
|
| 394 |
+
</list>
|
| 395 |
+
</value>
|
| 396 |
+
</entry>
|
| 397 |
+
<entry key="image_picker_platform_interface">
|
| 398 |
+
<value>
|
| 399 |
+
<list>
|
| 400 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_platform_interface-2.11.1/lib" />
|
| 401 |
+
</list>
|
| 402 |
+
</value>
|
| 403 |
+
</entry>
|
| 404 |
+
<entry key="image_picker_windows">
|
| 405 |
+
<value>
|
| 406 |
+
<list>
|
| 407 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_windows-0.2.2/lib" />
|
| 408 |
+
</list>
|
| 409 |
+
</value>
|
| 410 |
+
</entry>
|
| 411 |
+
<entry key="jni">
|
| 412 |
+
<value>
|
| 413 |
+
<list>
|
| 414 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jni-1.0.0/lib" />
|
| 415 |
+
</list>
|
| 416 |
+
</value>
|
| 417 |
+
</entry>
|
| 418 |
+
<entry key="jni_flutter">
|
| 419 |
+
<value>
|
| 420 |
+
<list>
|
| 421 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jni_flutter-1.0.1/lib" />
|
| 422 |
+
</list>
|
| 423 |
+
</value>
|
| 424 |
+
</entry>
|
| 425 |
+
<entry key="jovial_misc">
|
| 426 |
+
<value>
|
| 427 |
+
<list>
|
| 428 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jovial_misc-0.10.0/lib" />
|
| 429 |
+
</list>
|
| 430 |
+
</value>
|
| 431 |
+
</entry>
|
| 432 |
+
<entry key="jovial_svg">
|
| 433 |
+
<value>
|
| 434 |
+
<list>
|
| 435 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jovial_svg-1.1.29/lib" />
|
| 436 |
+
</list>
|
| 437 |
+
</value>
|
| 438 |
+
</entry>
|
| 439 |
+
<entry key="js">
|
| 440 |
+
<value>
|
| 441 |
+
<list>
|
| 442 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/js-0.6.7/lib" />
|
| 443 |
+
</list>
|
| 444 |
+
</value>
|
| 445 |
+
</entry>
|
| 446 |
+
<entry key="json_annotation">
|
| 447 |
+
<value>
|
| 448 |
+
<list>
|
| 449 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/json_annotation-4.12.0/lib" />
|
| 450 |
+
</list>
|
| 451 |
+
</value>
|
| 452 |
+
</entry>
|
| 453 |
+
<entry key="leak_tracker">
|
| 454 |
+
<value>
|
| 455 |
+
<list>
|
| 456 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2/lib" />
|
| 457 |
+
</list>
|
| 458 |
+
</value>
|
| 459 |
+
</entry>
|
| 460 |
+
<entry key="leak_tracker_flutter_testing">
|
| 461 |
+
<value>
|
| 462 |
+
<list>
|
| 463 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10/lib" />
|
| 464 |
+
</list>
|
| 465 |
+
</value>
|
| 466 |
+
</entry>
|
| 467 |
+
<entry key="leak_tracker_testing">
|
| 468 |
+
<value>
|
| 469 |
+
<list>
|
| 470 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2/lib" />
|
| 471 |
+
</list>
|
| 472 |
+
</value>
|
| 473 |
+
</entry>
|
| 474 |
+
<entry key="lints">
|
| 475 |
+
<value>
|
| 476 |
+
<list>
|
| 477 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.1.0/lib" />
|
| 478 |
+
</list>
|
| 479 |
+
</value>
|
| 480 |
+
</entry>
|
| 481 |
+
<entry key="logging">
|
| 482 |
+
<value>
|
| 483 |
+
<list>
|
| 484 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/logging-1.3.0/lib" />
|
| 485 |
+
</list>
|
| 486 |
+
</value>
|
| 487 |
+
</entry>
|
| 488 |
+
<entry key="matcher">
|
| 489 |
+
<value>
|
| 490 |
+
<list>
|
| 491 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.19/lib" />
|
| 492 |
+
</list>
|
| 493 |
+
</value>
|
| 494 |
+
</entry>
|
| 495 |
+
<entry key="material_color_utilities">
|
| 496 |
+
<value>
|
| 497 |
+
<list>
|
| 498 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.13.0/lib" />
|
| 499 |
+
</list>
|
| 500 |
+
</value>
|
| 501 |
+
</entry>
|
| 502 |
+
<entry key="meta">
|
| 503 |
+
<value>
|
| 504 |
+
<list>
|
| 505 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0/lib" />
|
| 506 |
+
</list>
|
| 507 |
+
</value>
|
| 508 |
+
</entry>
|
| 509 |
+
<entry key="mime">
|
| 510 |
+
<value>
|
| 511 |
+
<list>
|
| 512 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/mime-2.0.0/lib" />
|
| 513 |
+
</list>
|
| 514 |
+
</value>
|
| 515 |
+
</entry>
|
| 516 |
+
<entry key="native_toolchain_c">
|
| 517 |
+
<value>
|
| 518 |
+
<list>
|
| 519 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/native_toolchain_c-0.17.6/lib" />
|
| 520 |
+
</list>
|
| 521 |
+
</value>
|
| 522 |
+
</entry>
|
| 523 |
+
<entry key="objective_c">
|
| 524 |
+
<value>
|
| 525 |
+
<list>
|
| 526 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/objective_c-9.3.0/lib" />
|
| 527 |
+
</list>
|
| 528 |
+
</value>
|
| 529 |
+
</entry>
|
| 530 |
+
<entry key="package_config">
|
| 531 |
+
<value>
|
| 532 |
+
<list>
|
| 533 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/package_config-2.2.0/lib" />
|
| 534 |
+
</list>
|
| 535 |
+
</value>
|
| 536 |
+
</entry>
|
| 537 |
+
<entry key="path">
|
| 538 |
+
<value>
|
| 539 |
+
<list>
|
| 540 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1/lib" />
|
| 541 |
+
</list>
|
| 542 |
+
</value>
|
| 543 |
+
</entry>
|
| 544 |
+
<entry key="path_provider">
|
| 545 |
+
<value>
|
| 546 |
+
<list>
|
| 547 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider-2.1.5/lib" />
|
| 548 |
+
</list>
|
| 549 |
+
</value>
|
| 550 |
+
</entry>
|
| 551 |
+
<entry key="path_provider_android">
|
| 552 |
+
<value>
|
| 553 |
+
<list>
|
| 554 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_android-2.3.1/lib" />
|
| 555 |
+
</list>
|
| 556 |
+
</value>
|
| 557 |
+
</entry>
|
| 558 |
+
<entry key="path_provider_foundation">
|
| 559 |
+
<value>
|
| 560 |
+
<list>
|
| 561 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_foundation-2.6.0/lib" />
|
| 562 |
+
</list>
|
| 563 |
+
</value>
|
| 564 |
+
</entry>
|
| 565 |
+
<entry key="path_provider_linux">
|
| 566 |
+
<value>
|
| 567 |
+
<list>
|
| 568 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/lib" />
|
| 569 |
+
</list>
|
| 570 |
+
</value>
|
| 571 |
+
</entry>
|
| 572 |
+
<entry key="path_provider_platform_interface">
|
| 573 |
+
<value>
|
| 574 |
+
<list>
|
| 575 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/lib" />
|
| 576 |
+
</list>
|
| 577 |
+
</value>
|
| 578 |
+
</entry>
|
| 579 |
+
<entry key="path_provider_windows">
|
| 580 |
+
<value>
|
| 581 |
+
<list>
|
| 582 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib" />
|
| 583 |
+
</list>
|
| 584 |
+
</value>
|
| 585 |
+
</entry>
|
| 586 |
+
<entry key="petitparser">
|
| 587 |
+
<value>
|
| 588 |
+
<list>
|
| 589 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/petitparser-7.0.2/lib" />
|
| 590 |
+
</list>
|
| 591 |
+
</value>
|
| 592 |
+
</entry>
|
| 593 |
+
<entry key="platform">
|
| 594 |
+
<value>
|
| 595 |
+
<list>
|
| 596 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/platform-3.1.6/lib" />
|
| 597 |
+
</list>
|
| 598 |
+
</value>
|
| 599 |
+
</entry>
|
| 600 |
+
<entry key="plugin_platform_interface">
|
| 601 |
+
<value>
|
| 602 |
+
<list>
|
| 603 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib" />
|
| 604 |
+
</list>
|
| 605 |
+
</value>
|
| 606 |
+
</entry>
|
| 607 |
+
<entry key="posix">
|
| 608 |
+
<value>
|
| 609 |
+
<list>
|
| 610 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/posix-6.5.0/lib" />
|
| 611 |
+
</list>
|
| 612 |
+
</value>
|
| 613 |
+
</entry>
|
| 614 |
+
<entry key="pub_semver">
|
| 615 |
+
<value>
|
| 616 |
+
<list>
|
| 617 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pub_semver-2.2.0/lib" />
|
| 618 |
+
</list>
|
| 619 |
+
</value>
|
| 620 |
+
</entry>
|
| 621 |
+
<entry key="record_use">
|
| 622 |
+
<value>
|
| 623 |
+
<list>
|
| 624 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/record_use-0.6.0/lib" />
|
| 625 |
+
</list>
|
| 626 |
+
</value>
|
| 627 |
+
</entry>
|
| 628 |
+
<entry key="riverpod">
|
| 629 |
+
<value>
|
| 630 |
+
<list>
|
| 631 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/riverpod-2.6.1/lib" />
|
| 632 |
+
</list>
|
| 633 |
+
</value>
|
| 634 |
+
</entry>
|
| 635 |
+
<entry key="sky_engine">
|
| 636 |
+
<value>
|
| 637 |
+
<list>
|
| 638 |
+
<option value="$PROJECT_DIR$/../flutter/bin/cache/pkg/sky_engine/lib" />
|
| 639 |
+
</list>
|
| 640 |
+
</value>
|
| 641 |
+
</entry>
|
| 642 |
+
<entry key="source_span">
|
| 643 |
+
<value>
|
| 644 |
+
<list>
|
| 645 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.2/lib" />
|
| 646 |
+
</list>
|
| 647 |
+
</value>
|
| 648 |
+
</entry>
|
| 649 |
+
<entry key="stack_trace">
|
| 650 |
+
<value>
|
| 651 |
+
<list>
|
| 652 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1/lib" />
|
| 653 |
+
</list>
|
| 654 |
+
</value>
|
| 655 |
+
</entry>
|
| 656 |
+
<entry key="state_notifier">
|
| 657 |
+
<value>
|
| 658 |
+
<list>
|
| 659 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/state_notifier-1.0.0/lib" />
|
| 660 |
+
</list>
|
| 661 |
+
</value>
|
| 662 |
+
</entry>
|
| 663 |
+
<entry key="stream_channel">
|
| 664 |
+
<value>
|
| 665 |
+
<list>
|
| 666 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4/lib" />
|
| 667 |
+
</list>
|
| 668 |
+
</value>
|
| 669 |
+
</entry>
|
| 670 |
+
<entry key="string_scanner">
|
| 671 |
+
<value>
|
| 672 |
+
<list>
|
| 673 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1/lib" />
|
| 674 |
+
</list>
|
| 675 |
+
</value>
|
| 676 |
+
</entry>
|
| 677 |
+
<entry key="term_glyph">
|
| 678 |
+
<value>
|
| 679 |
+
<list>
|
| 680 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2/lib" />
|
| 681 |
+
</list>
|
| 682 |
+
</value>
|
| 683 |
+
</entry>
|
| 684 |
+
<entry key="test_api">
|
| 685 |
+
<value>
|
| 686 |
+
<list>
|
| 687 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.10/lib" />
|
| 688 |
+
</list>
|
| 689 |
+
</value>
|
| 690 |
+
</entry>
|
| 691 |
+
<entry key="typed_data">
|
| 692 |
+
<value>
|
| 693 |
+
<list>
|
| 694 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib" />
|
| 695 |
+
</list>
|
| 696 |
+
</value>
|
| 697 |
+
</entry>
|
| 698 |
+
<entry key="url_launcher">
|
| 699 |
+
<value>
|
| 700 |
+
<list>
|
| 701 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher-6.3.2/lib" />
|
| 702 |
+
</list>
|
| 703 |
+
</value>
|
| 704 |
+
</entry>
|
| 705 |
+
<entry key="url_launcher_android">
|
| 706 |
+
<value>
|
| 707 |
+
<list>
|
| 708 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_android-6.3.29/lib" />
|
| 709 |
+
</list>
|
| 710 |
+
</value>
|
| 711 |
+
</entry>
|
| 712 |
+
<entry key="url_launcher_ios">
|
| 713 |
+
<value>
|
| 714 |
+
<list>
|
| 715 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_ios-6.4.1/lib" />
|
| 716 |
+
</list>
|
| 717 |
+
</value>
|
| 718 |
+
</entry>
|
| 719 |
+
<entry key="url_launcher_linux">
|
| 720 |
+
<value>
|
| 721 |
+
<list>
|
| 722 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.2.2/lib" />
|
| 723 |
+
</list>
|
| 724 |
+
</value>
|
| 725 |
+
</entry>
|
| 726 |
+
<entry key="url_launcher_macos">
|
| 727 |
+
<value>
|
| 728 |
+
<list>
|
| 729 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_macos-3.2.5/lib" />
|
| 730 |
+
</list>
|
| 731 |
+
</value>
|
| 732 |
+
</entry>
|
| 733 |
+
<entry key="url_launcher_platform_interface">
|
| 734 |
+
<value>
|
| 735 |
+
<list>
|
| 736 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2/lib" />
|
| 737 |
+
</list>
|
| 738 |
+
</value>
|
| 739 |
+
</entry>
|
| 740 |
+
<entry key="url_launcher_web">
|
| 741 |
+
<value>
|
| 742 |
+
<list>
|
| 743 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_web-2.4.2/lib" />
|
| 744 |
+
</list>
|
| 745 |
+
</value>
|
| 746 |
+
</entry>
|
| 747 |
+
<entry key="url_launcher_windows">
|
| 748 |
+
<value>
|
| 749 |
+
<list>
|
| 750 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_windows-3.1.5/lib" />
|
| 751 |
+
</list>
|
| 752 |
+
</value>
|
| 753 |
+
</entry>
|
| 754 |
+
<entry key="vector_math">
|
| 755 |
+
<value>
|
| 756 |
+
<list>
|
| 757 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0/lib" />
|
| 758 |
+
</list>
|
| 759 |
+
</value>
|
| 760 |
+
</entry>
|
| 761 |
+
<entry key="video_player">
|
| 762 |
+
<value>
|
| 763 |
+
<list>
|
| 764 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player-2.11.1/lib" />
|
| 765 |
+
</list>
|
| 766 |
+
</value>
|
| 767 |
+
</entry>
|
| 768 |
+
<entry key="video_player_android">
|
| 769 |
+
<value>
|
| 770 |
+
<list>
|
| 771 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_android-2.9.5/lib" />
|
| 772 |
+
</list>
|
| 773 |
+
</value>
|
| 774 |
+
</entry>
|
| 775 |
+
<entry key="video_player_avfoundation">
|
| 776 |
+
<value>
|
| 777 |
+
<list>
|
| 778 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_avfoundation-2.9.6/lib" />
|
| 779 |
+
</list>
|
| 780 |
+
</value>
|
| 781 |
+
</entry>
|
| 782 |
+
<entry key="video_player_platform_interface">
|
| 783 |
+
<value>
|
| 784 |
+
<list>
|
| 785 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_platform_interface-6.7.0/lib" />
|
| 786 |
+
</list>
|
| 787 |
+
</value>
|
| 788 |
+
</entry>
|
| 789 |
+
<entry key="video_player_web">
|
| 790 |
+
<value>
|
| 791 |
+
<list>
|
| 792 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_web-2.4.0/lib" />
|
| 793 |
+
</list>
|
| 794 |
+
</value>
|
| 795 |
+
</entry>
|
| 796 |
+
<entry key="vm_service">
|
| 797 |
+
<value>
|
| 798 |
+
<list>
|
| 799 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.1.0/lib" />
|
| 800 |
+
</list>
|
| 801 |
+
</value>
|
| 802 |
+
</entry>
|
| 803 |
+
<entry key="web">
|
| 804 |
+
<value>
|
| 805 |
+
<list>
|
| 806 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/web-1.1.1/lib" />
|
| 807 |
+
</list>
|
| 808 |
+
</value>
|
| 809 |
+
</entry>
|
| 810 |
+
<entry key="win32">
|
| 811 |
+
<value>
|
| 812 |
+
<list>
|
| 813 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/win32-5.15.0/lib" />
|
| 814 |
+
</list>
|
| 815 |
+
</value>
|
| 816 |
+
</entry>
|
| 817 |
+
<entry key="window_to_front">
|
| 818 |
+
<value>
|
| 819 |
+
<list>
|
| 820 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/window_to_front-0.0.3/lib" />
|
| 821 |
+
</list>
|
| 822 |
+
</value>
|
| 823 |
+
</entry>
|
| 824 |
+
<entry key="xdg_directories">
|
| 825 |
+
<value>
|
| 826 |
+
<list>
|
| 827 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xdg_directories-1.1.0/lib" />
|
| 828 |
+
</list>
|
| 829 |
+
</value>
|
| 830 |
+
</entry>
|
| 831 |
+
<entry key="xml">
|
| 832 |
+
<value>
|
| 833 |
+
<list>
|
| 834 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xml-6.6.1/lib" />
|
| 835 |
+
</list>
|
| 836 |
+
</value>
|
| 837 |
+
</entry>
|
| 838 |
+
<entry key="yaml">
|
| 839 |
+
<value>
|
| 840 |
+
<list>
|
| 841 |
+
<option value="$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/yaml-3.1.3/lib" />
|
| 842 |
+
</list>
|
| 843 |
+
</value>
|
| 844 |
+
</entry>
|
| 845 |
+
</option>
|
| 846 |
+
</properties>
|
| 847 |
+
<CLASSES>
|
| 848 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/archive-4.0.9/lib" />
|
| 849 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/args-2.7.0/lib" />
|
| 850 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/async-2.13.1/lib" />
|
| 851 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/boolean_selector-2.1.2/lib" />
|
| 852 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/characters-1.4.1/lib" />
|
| 853 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/checked_yaml-2.0.4/lib" />
|
| 854 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cli_util-0.4.2/lib" />
|
| 855 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/clock-1.1.2/lib" />
|
| 856 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/code_assets-1.0.0/lib" />
|
| 857 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/collection-1.19.1/lib" />
|
| 858 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/convert-3.1.2/lib" />
|
| 859 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/country_flags-4.1.2/lib" />
|
| 860 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cross_file-0.3.5+2/lib" />
|
| 861 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/crypto-3.0.7/lib" />
|
| 862 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/csslib-1.0.2/lib" />
|
| 863 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/cupertino_icons-1.0.9/lib" />
|
| 864 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/desktop_webview_window-0.2.3/lib" />
|
| 865 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio-5.9.2/lib" />
|
| 866 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/dio_web_adapter-2.1.2/lib" />
|
| 867 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/fake_async-1.3.3/lib" />
|
| 868 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/ffi-2.2.0/lib" />
|
| 869 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file-7.0.1/lib" />
|
| 870 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_linux-0.9.4/lib" />
|
| 871 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_macos-0.9.5/lib" />
|
| 872 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_platform_interface-2.7.0/lib" />
|
| 873 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/file_selector_windows-0.9.3+5/lib" />
|
| 874 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_launcher_icons-0.14.4/lib" />
|
| 875 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_lints-6.0.0/lib" />
|
| 876 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_plugin_android_lifecycle-2.0.34/lib" />
|
| 877 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_riverpod-2.6.1/lib" />
|
| 878 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage-9.2.4/lib" />
|
| 879 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_linux-1.2.3/lib" />
|
| 880 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_macos-3.1.3/lib" />
|
| 881 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_platform_interface-1.1.2/lib" />
|
| 882 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_web-1.2.1/lib" />
|
| 883 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_secure_storage_windows-3.1.2/lib" />
|
| 884 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_tts-4.2.5/lib" />
|
| 885 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_web_auth_2-4.1.0/lib" />
|
| 886 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/flutter_web_auth_2_platform_interface-4.1.0/lib" />
|
| 887 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/glob-2.1.3/lib" />
|
| 888 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/go_router-14.8.1/lib" />
|
| 889 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/google_fonts-6.3.3/lib" />
|
| 890 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/hooks-1.0.3/lib" />
|
| 891 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/html-0.15.6/lib" />
|
| 892 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http-1.6.0/lib" />
|
| 893 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/http_parser-4.1.2/lib" />
|
| 894 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image-4.8.0/lib" />
|
| 895 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker-1.2.1/lib" />
|
| 896 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_android-0.8.13+16/lib" />
|
| 897 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_for_web-3.1.1/lib" />
|
| 898 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_ios-0.8.13+6/lib" />
|
| 899 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_linux-0.2.2/lib" />
|
| 900 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_macos-0.2.2+1/lib" />
|
| 901 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_platform_interface-2.11.1/lib" />
|
| 902 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/image_picker_windows-0.2.2/lib" />
|
| 903 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jni-1.0.0/lib" />
|
| 904 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jni_flutter-1.0.1/lib" />
|
| 905 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jovial_misc-0.10.0/lib" />
|
| 906 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/jovial_svg-1.1.29/lib" />
|
| 907 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/js-0.6.7/lib" />
|
| 908 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/json_annotation-4.12.0/lib" />
|
| 909 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker-11.0.2/lib" />
|
| 910 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_flutter_testing-3.0.10/lib" />
|
| 911 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/leak_tracker_testing-3.0.2/lib" />
|
| 912 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/lints-6.1.0/lib" />
|
| 913 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/logging-1.3.0/lib" />
|
| 914 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/matcher-0.12.19/lib" />
|
| 915 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/material_color_utilities-0.13.0/lib" />
|
| 916 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/meta-1.17.0/lib" />
|
| 917 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/mime-2.0.0/lib" />
|
| 918 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/native_toolchain_c-0.17.6/lib" />
|
| 919 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/objective_c-9.3.0/lib" />
|
| 920 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/package_config-2.2.0/lib" />
|
| 921 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path-1.9.1/lib" />
|
| 922 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider-2.1.5/lib" />
|
| 923 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_android-2.3.1/lib" />
|
| 924 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_foundation-2.6.0/lib" />
|
| 925 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_linux-2.2.1/lib" />
|
| 926 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_platform_interface-2.1.2/lib" />
|
| 927 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/path_provider_windows-2.3.0/lib" />
|
| 928 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/petitparser-7.0.2/lib" />
|
| 929 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/platform-3.1.6/lib" />
|
| 930 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/plugin_platform_interface-2.1.8/lib" />
|
| 931 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/posix-6.5.0/lib" />
|
| 932 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/pub_semver-2.2.0/lib" />
|
| 933 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/record_use-0.6.0/lib" />
|
| 934 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/riverpod-2.6.1/lib" />
|
| 935 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/source_span-1.10.2/lib" />
|
| 936 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stack_trace-1.12.1/lib" />
|
| 937 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/state_notifier-1.0.0/lib" />
|
| 938 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/stream_channel-2.1.4/lib" />
|
| 939 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/string_scanner-1.4.1/lib" />
|
| 940 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/term_glyph-1.2.2/lib" />
|
| 941 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/test_api-0.7.10/lib" />
|
| 942 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/typed_data-1.4.0/lib" />
|
| 943 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher-6.3.2/lib" />
|
| 944 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_android-6.3.29/lib" />
|
| 945 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_ios-6.4.1/lib" />
|
| 946 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_linux-3.2.2/lib" />
|
| 947 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_macos-3.2.5/lib" />
|
| 948 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_platform_interface-2.3.2/lib" />
|
| 949 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_web-2.4.2/lib" />
|
| 950 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/url_launcher_windows-3.1.5/lib" />
|
| 951 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vector_math-2.2.0/lib" />
|
| 952 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player-2.11.1/lib" />
|
| 953 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_android-2.9.5/lib" />
|
| 954 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_avfoundation-2.9.6/lib" />
|
| 955 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_platform_interface-6.7.0/lib" />
|
| 956 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/video_player_web-2.4.0/lib" />
|
| 957 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/vm_service-15.1.0/lib" />
|
| 958 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/web-1.1.1/lib" />
|
| 959 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/win32-5.15.0/lib" />
|
| 960 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/window_to_front-0.0.3/lib" />
|
| 961 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xdg_directories-1.1.0/lib" />
|
| 962 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/xml-6.6.1/lib" />
|
| 963 |
+
<root url="file://$USER_HOME$/AppData/Local/Pub/Cache/hosted/pub.dev/yaml-3.1.3/lib" />
|
| 964 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/pkg/sky_engine/lib" />
|
| 965 |
+
<root url="file://$PROJECT_DIR$/../flutter/packages/flutter/lib" />
|
| 966 |
+
<root url="file://$PROJECT_DIR$/../flutter/packages/flutter_test/lib" />
|
| 967 |
+
<root url="file://$PROJECT_DIR$/../flutter/packages/flutter_web_plugins/lib" />
|
| 968 |
+
</CLASSES>
|
| 969 |
+
<JAVADOC />
|
| 970 |
+
<SOURCES />
|
| 971 |
+
</library>
|
| 972 |
+
</component>
|
.idea/libraries/Dart_SDK.xml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<component name="libraryTable">
|
| 2 |
+
<library name="Dart SDK">
|
| 3 |
+
<CLASSES>
|
| 4 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/_internal" />
|
| 5 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/async" />
|
| 6 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/cli" />
|
| 7 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/collection" />
|
| 8 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/concurrent" />
|
| 9 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/convert" />
|
| 10 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/core" />
|
| 11 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/developer" />
|
| 12 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/ffi" />
|
| 13 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/html" />
|
| 14 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/indexed_db" />
|
| 15 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/io" />
|
| 16 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/isolate" />
|
| 17 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/js" />
|
| 18 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/js_interop" />
|
| 19 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/js_interop_unsafe" />
|
| 20 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/js_util" />
|
| 21 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/math" />
|
| 22 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/mirrors" />
|
| 23 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/svg" />
|
| 24 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/typed_data" />
|
| 25 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/web_audio" />
|
| 26 |
+
<root url="file://$PROJECT_DIR$/../flutter/bin/cache/dart-sdk/lib/web_gl" />
|
| 27 |
+
</CLASSES>
|
| 28 |
+
<JAVADOC />
|
| 29 |
+
<SOURCES />
|
| 30 |
+
</library>
|
| 31 |
+
</component>
|
.idea/markdown.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="MarkdownSettings">
|
| 4 |
+
<option name="previewPanelProviderInfo">
|
| 5 |
+
<ProviderInfo name="Compose (experimental)" className="com.intellij.markdown.compose.preview.ComposePanelProvider" />
|
| 6 |
+
</option>
|
| 7 |
+
</component>
|
| 8 |
+
</project>
|
.idea/misc.xml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<project version="4">
|
| 2 |
+
<component name="ProjectRootManager" version="2">
|
| 3 |
+
<output url="file://$PROJECT_DIR$/out" />
|
| 4 |
+
</component>
|
| 5 |
+
</project>
|
.idea/modules.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="ProjectModuleManager">
|
| 4 |
+
<modules>
|
| 5 |
+
<module fileurl="file://$PROJECT_DIR$/.idea/language-learner.iml" filepath="$PROJECT_DIR$/.idea/language-learner.iml" />
|
| 6 |
+
</modules>
|
| 7 |
+
</component>
|
| 8 |
+
</project>
|
.idea/vcs.xml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<project version="4">
|
| 3 |
+
<component name="VcsDirectoryMappings">
|
| 4 |
+
<mapping directory="" vcs="Git" />
|
| 5 |
+
</component>
|
| 6 |
+
</project>
|
.mcp.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mcpServers": {
|
| 3 |
+
"shadcn": {
|
| 4 |
+
"command": "npx",
|
| 5 |
+
"args": [
|
| 6 |
+
"shadcn@latest",
|
| 7 |
+
"mcp"
|
| 8 |
+
]
|
| 9 |
+
}
|
| 10 |
+
}
|
| 11 |
+
}
|
AGENTS.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!-- BEGIN:nextjs-agent-rules -->
|
| 2 |
+
# This is NOT the Next.js you know
|
| 3 |
+
|
| 4 |
+
This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
|
| 5 |
+
<!-- END:nextjs-agent-rules -->
|
CLAUDE.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
@AGENTS.md
|
Dockerfile
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# syntax=docker/dockerfile:1.7
|
| 2 |
+
|
| 3 |
+
# ---- deps ----
|
| 4 |
+
FROM node:20-alpine AS deps
|
| 5 |
+
WORKDIR /app
|
| 6 |
+
RUN apk add --no-cache libc6-compat
|
| 7 |
+
COPY package.json package-lock.json ./
|
| 8 |
+
RUN npm ci --no-audit --no-fund
|
| 9 |
+
|
| 10 |
+
# ---- builder ----
|
| 11 |
+
FROM node:20-alpine AS builder
|
| 12 |
+
WORKDIR /app
|
| 13 |
+
RUN apk add --no-cache libc6-compat
|
| 14 |
+
COPY --from=deps /app/node_modules ./node_modules
|
| 15 |
+
COPY . .
|
| 16 |
+
|
| 17 |
+
# lib/db/client.ts throws at import time if DATABASE_URL is unset, and
|
| 18 |
+
# `next build` evaluates server modules. Provide a placeholder so the build
|
| 19 |
+
# succeeds. Real value (if any) is injected at runtime via HF Space Secrets.
|
| 20 |
+
ENV DATABASE_URL=postgresql://placeholder:placeholder@127.0.0.1:5432/placeholder
|
| 21 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 22 |
+
ENV NODE_ENV=production
|
| 23 |
+
|
| 24 |
+
RUN npm run build
|
| 25 |
+
|
| 26 |
+
# ---- runner ----
|
| 27 |
+
FROM node:20-alpine AS runner
|
| 28 |
+
WORKDIR /app
|
| 29 |
+
RUN apk add --no-cache libc6-compat \
|
| 30 |
+
&& addgroup -g 1001 -S nodejs \
|
| 31 |
+
&& adduser -u 1001 -S nextjs -G nodejs
|
| 32 |
+
|
| 33 |
+
ENV NODE_ENV=production
|
| 34 |
+
ENV NEXT_TELEMETRY_DISABLED=1
|
| 35 |
+
# HF Spaces routes traffic to $PORT (defaults to 7860). Bind to 0.0.0.0
|
| 36 |
+
# so the container is reachable from outside.
|
| 37 |
+
ENV PORT=7860
|
| 38 |
+
ENV HOSTNAME=0.0.0.0
|
| 39 |
+
|
| 40 |
+
COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
|
| 41 |
+
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
| 42 |
+
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
| 43 |
+
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
| 44 |
+
|
| 45 |
+
USER nextjs
|
| 46 |
+
EXPOSE 7860
|
| 47 |
+
|
| 48 |
+
CMD ["npx", "next", "start", "-H", "0.0.0.0", "-p", "7860"]
|
README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: PraxaLing
|
| 3 |
+
emoji: 🦜
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
pinned: false
|
| 9 |
+
hf_oauth: true
|
| 10 |
+
hf_oauth_scopes:
|
| 11 |
+
- email
|
| 12 |
+
- inference-api
|
| 13 |
+
---
|
| 14 |
+
|
| 15 |
+
# PraxaLing
|
| 16 |
+
|
| 17 |
+
Learn languages through short stories and the world around you, powered by HuggingFace for identity and inference.
|
| 18 |
+
|
| 19 |
+
Two clients, one backend:
|
| 20 |
+
- **Web** — Next.js 16 App Router (also acts as the shared API).
|
| 21 |
+
- **Mobile** — Flutter (latest stable), calls the same HTTP endpoints.
|
| 22 |
+
|
| 23 |
+
Three flows:
|
| 24 |
+
1. **Sign in with HuggingFace** (OAuth).
|
| 25 |
+
2. **Practice** — read short stories matched to your CEFR level and target language; tap any word for a translation. Generate new stories on demand.
|
| 26 |
+
3. **Camera** — take or upload a photo; the backend runs BLIP captioning, DETR detection, NLLB translation, and Mistral-generated example sentences.
|
| 27 |
+
|
| 28 |
+
## Stack
|
| 29 |
+
|
| 30 |
+
| Concern | Tech |
|
| 31 |
+
|---|---|
|
| 32 |
+
| Web | Next.js 16.2 · React 19 · Tailwind v4 · Turbopack |
|
| 33 |
+
| Mobile | Flutter 3.41 · Riverpod · go_router · dio |
|
| 34 |
+
| Auth | HuggingFace OAuth (`arctic`) · JWT (`jose`) |
|
| 35 |
+
| DB | Postgres · Drizzle ORM |
|
| 36 |
+
| Storage | Vercel Blob (optional) |
|
| 37 |
+
| Inference | `@huggingface/inference` · Mistral-7B-Instruct · BLIP · DETR · NLLB-200 |
|
| 38 |
+
|
| 39 |
+
## Getting started (web)
|
| 40 |
+
|
| 41 |
+
```bash
|
| 42 |
+
# 1. Install deps
|
| 43 |
+
npm install
|
| 44 |
+
|
| 45 |
+
# 2. Bring up Postgres
|
| 46 |
+
docker compose up -d
|
| 47 |
+
|
| 48 |
+
# 3. Configure env — copy and fill in
|
| 49 |
+
cp .env.example .env.local
|
| 50 |
+
|
| 51 |
+
# 4. Apply schema
|
| 52 |
+
npm run db:push
|
| 53 |
+
|
| 54 |
+
# 5. (Optional) seed stories
|
| 55 |
+
npm run db:seed
|
| 56 |
+
|
| 57 |
+
# 6. Run dev server
|
| 58 |
+
npm run dev
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
Required env:
|
| 62 |
+
- `AUTH_SECRET` — any 32+ char random string (`openssl rand -base64 32`)
|
| 63 |
+
- `HF_OAUTH_CLIENT_ID` / `HF_OAUTH_CLIENT_SECRET` — from https://huggingface.co/settings/applications/new, or use the Space metadata `hf_oauth: true` and the app will read Hugging Face's injected `OAUTH_CLIENT_ID` / `OAUTH_CLIENT_SECRET`
|
| 64 |
+
- `HF_TOKEN` — read token from https://huggingface.co/settings/tokens
|
| 65 |
+
- Optional: `BLOB_READ_WRITE_TOKEN` for Vercel Blob
|
| 66 |
+
|
| 67 |
+
Redirect URIs to register on the HF OAuth app:
|
| 68 |
+
- `http://localhost:3000/api/auth/callback/huggingface`
|
| 69 |
+
- `http://localhost:3000/api/auth/callback/huggingface?client=mobile`
|
| 70 |
+
- `https://<SPACE_HOST>/api/auth/callback/huggingface`
|
| 71 |
+
- `https://<SPACE_HOST>/api/auth/callback/huggingface?client=mobile`
|
| 72 |
+
|
| 73 |
+
For the hosted Space, use the direct `*.hf.space` host, not the `huggingface.co/spaces/...` page, for redirect URIs and mobile `API_BASE_URL`.
|
| 74 |
+
|
| 75 |
+
## Getting started (mobile)
|
| 76 |
+
|
| 77 |
+
```bash
|
| 78 |
+
cd mobile
|
| 79 |
+
flutter pub get
|
| 80 |
+
|
| 81 |
+
# iOS simulator — host loopback is 127.0.0.1 by default
|
| 82 |
+
flutter run --dart-define=API_BASE_URL=http://127.0.0.1:3000
|
| 83 |
+
|
| 84 |
+
# Android emulator — host loopback is 10.0.2.2 (default)
|
| 85 |
+
flutter run
|
| 86 |
+
|
| 87 |
+
# Hosted backend / physical device
|
| 88 |
+
flutter run --dart-define=API_BASE_URL=https://<SPACE_HOST>
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
See `docs/API.md` for the endpoints the app consumes.
|
| 92 |
+
|
| 93 |
+
## Architecture notes
|
| 94 |
+
|
| 95 |
+
- **Auth**: both clients exchange an HF OAuth code for our own short-lived JWT. Web stores it in an httpOnly cookie; mobile in `flutter_secure_storage`. `lib/auth/session.ts#getSession()` handles both.
|
| 96 |
+
- **Next.js 16 specifics**: `middleware.ts` is now `proxy.ts`; dynamic APIs (`cookies`, `headers`, `params`) are async; Turbopack is default. See `node_modules/next/dist/docs/01-app/02-guides/upgrading/version-16.md`.
|
| 97 |
+
- **Data flow for camera**: multipart → BLIP + DETR in parallel → dedupe labels → translate each to target lang → Mistral generates 3 sentences keyed on caption + objects → persisted in `vision_jobs`.
|
| 98 |
+
|
| 99 |
+
## Scripts
|
| 100 |
+
|
| 101 |
+
| Command | Purpose |
|
| 102 |
+
|---|---|
|
| 103 |
+
| `npm run dev` | Next.js dev server |
|
| 104 |
+
| `npm run build` | Production build |
|
| 105 |
+
| `npm run db:push` | Apply schema to Postgres |
|
| 106 |
+
| `npm run db:generate` | Generate migration from schema |
|
| 107 |
+
| `npm run db:migrate` | Apply pending migrations |
|
| 108 |
+
| `npm run db:seed` | Insert hand-curated stories |
|
| 109 |
+
| `npm run db:studio` | Drizzle Studio UI |
|
app/(app)/BottomNavLink.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
import { usePathname } from "next/navigation";
|
| 5 |
+
|
| 6 |
+
export function BottomNavLink({ href, icon, label }: { href: string; icon: React.ReactNode; label: string }) {
|
| 7 |
+
const pathname = usePathname();
|
| 8 |
+
const isActive = pathname === href || pathname.startsWith(`${href}/`);
|
| 9 |
+
|
| 10 |
+
return (
|
| 11 |
+
<Link
|
| 12 |
+
href={href}
|
| 13 |
+
aria-current={isActive ? "page" : undefined}
|
| 14 |
+
className={`flex min-h-14 flex-col items-center justify-center gap-1 rounded-xl text-xs font-black uppercase tracking-tight transition-all sm:flex-row sm:gap-2 sm:text-sm ${
|
| 15 |
+
isActive
|
| 16 |
+
? "border-2 border-black bg-[#FFD21E] text-black shadow-[2px_2px_0px_rgba(0,0,0,1)]"
|
| 17 |
+
: "text-gray-500 hover:bg-[#FFF7D6] hover:text-black"
|
| 18 |
+
}`}
|
| 19 |
+
>
|
| 20 |
+
{icon}
|
| 21 |
+
{label}
|
| 22 |
+
</Link>
|
| 23 |
+
);
|
| 24 |
+
}
|
app/(app)/camera/AnalysisResults.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { motion } from "framer-motion";
|
| 4 |
+
import { Furi } from "@/components/Furi";
|
| 5 |
+
import { TTSButton } from "@/components/TTSButton";
|
| 6 |
+
import type { Analysis } from "./types";
|
| 7 |
+
|
| 8 |
+
const EASE = [0.16, 1, 0.3, 1] as const;
|
| 9 |
+
|
| 10 |
+
export function AnalysisResults({ analysis, lang }: { analysis: Analysis; lang: string }) {
|
| 11 |
+
return (
|
| 12 |
+
<motion.div
|
| 13 |
+
initial={{ opacity: 0, y: 16 }}
|
| 14 |
+
animate={{ opacity: 1, y: 0 }}
|
| 15 |
+
transition={{ duration: 0.5, ease: EASE }}
|
| 16 |
+
className="grid gap-8 lg:grid-cols-2"
|
| 17 |
+
>
|
| 18 |
+
<ResultCard title="Objects detected">
|
| 19 |
+
{analysis.objects.length > 0 ? (
|
| 20 |
+
<ul className="space-y-4">
|
| 21 |
+
{analysis.objects.map((o, i) => (
|
| 22 |
+
<li key={`${o.translation}-row-${i}`} className="rounded-xl bg-white border-2 border-black flex items-center justify-between gap-4 p-4 transition-all shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 23 |
+
<div className="flex items-center gap-3 min-w-0">
|
| 24 |
+
<TTSButton text={o.translation} lang={lang} />
|
| 25 |
+
<div>
|
| 26 |
+
<div className={`text-lg font-black text-black ${o.translationSegments ? "has-furi" : ""}`}>
|
| 27 |
+
<Furi text={o.translation} segments={o.translationSegments} />
|
| 28 |
+
</div>
|
| 29 |
+
{o.romanized ? <div className="text-xs font-bold text-[#3B82F6] mt-0.5">{o.romanized}</div> : null}
|
| 30 |
+
<div className="mt-1.5"><span className="text-[10px] font-black uppercase tracking-wider text-black bg-[#E6FBFA] border border-black rounded px-1.5 py-0.5 shadow-[1px_1px_0px_rgba(0,0,0,1)]">{o.label}</span></div>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
<div className="rounded-lg border-2 border-black bg-[#FFD21E] px-3 py-1 text-xs font-black text-black shadow-[2px_2px_0px_rgba(0,0,0,1)] shrink-0">
|
| 34 |
+
{Math.round(o.score * 100)}%
|
| 35 |
+
</div>
|
| 36 |
+
</li>
|
| 37 |
+
))}
|
| 38 |
+
</ul>
|
| 39 |
+
) : (
|
| 40 |
+
<p className="text-sm font-bold text-gray-500">No objects detected. Try a clearer photo.</p>
|
| 41 |
+
)}
|
| 42 |
+
</ResultCard>
|
| 43 |
+
|
| 44 |
+
<ResultCard title="Practice sentences">
|
| 45 |
+
{analysis.sentences.length > 0 ? (
|
| 46 |
+
<ul className="space-y-4">
|
| 47 |
+
{analysis.sentences.map((s, i) => (
|
| 48 |
+
<li key={`${s.target}-${i}`} className="rounded-xl bg-white border-2 border-black p-4 transition-all shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 49 |
+
<div className="flex items-start justify-between gap-3">
|
| 50 |
+
<div className={`text-lg font-black text-black ${s.targetSegments ? "has-furi" : ""}`}>
|
| 51 |
+
<Furi text={s.target} segments={s.targetSegments} />
|
| 52 |
+
</div>
|
| 53 |
+
<TTSButton text={s.target} lang={lang} />
|
| 54 |
+
</div>
|
| 55 |
+
{s.romanized ? <div className="mt-1 text-sm font-bold italic text-[#3B82F6]">{s.romanized}</div> : null}
|
| 56 |
+
<div className="mt-3 text-xs font-black text-black bg-[#FFF7D6] p-2.5 rounded-lg border-2 border-black shadow-[2px_2px_0px_rgba(0,0,0,1)]">{s.gloss}</div>
|
| 57 |
+
</li>
|
| 58 |
+
))}
|
| 59 |
+
</ul>
|
| 60 |
+
) : (
|
| 61 |
+
<p className="text-sm font-bold text-gray-500">No sentences generated. Retry the photo.</p>
|
| 62 |
+
)}
|
| 63 |
+
</ResultCard>
|
| 64 |
+
</motion.div>
|
| 65 |
+
);
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
function ResultCard({ title, children }: { title: string; children: React.ReactNode }) {
|
| 69 |
+
return (
|
| 70 |
+
<section className="rounded-2xl bg-white p-6 border-3 border-black shadow-[6px_6px_0px_rgba(0,0,0,1)]">
|
| 71 |
+
<h2 className="text-sm font-black uppercase tracking-wider text-black mb-6 flex items-center gap-2">
|
| 72 |
+
<span className="w-2 h-6 bg-[#FFD21E] border-2 border-black rounded-full" />
|
| 73 |
+
{title}
|
| 74 |
+
</h2>
|
| 75 |
+
{children}
|
| 76 |
+
</section>
|
| 77 |
+
);
|
| 78 |
+
}
|
app/(app)/camera/CameraClient.tsx
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useRef, useState } from "react";
|
| 4 |
+
import { motion } from "framer-motion";
|
| 5 |
+
import type { Level } from "@/lib/languages";
|
| 6 |
+
import { LevelPicker } from "@/components/LevelPicker";
|
| 7 |
+
import { PhotoMarkers } from "./PhotoMarkers";
|
| 8 |
+
import { UploadDropzone } from "./UploadDropzone";
|
| 9 |
+
import { AnalysisResults } from "./AnalysisResults";
|
| 10 |
+
import type { Analysis } from "./types";
|
| 11 |
+
import { Camera, RotateCcw, Wand2, AlertCircle } from "lucide-react";
|
| 12 |
+
|
| 13 |
+
export function CameraClient({ defaultLevel, lang }: { defaultLevel: Level; lang: string }) {
|
| 14 |
+
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
| 15 |
+
const [imageSize, setImageSize] = useState<{ w: number; h: number } | null>(null);
|
| 16 |
+
const [analysis, setAnalysis] = useState<Analysis | null>(null);
|
| 17 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 18 |
+
const [error, setError] = useState<string | null>(null);
|
| 19 |
+
const [level, setLevel] = useState<Level>(defaultLevel);
|
| 20 |
+
const inputRef = useRef<HTMLInputElement>(null);
|
| 21 |
+
// Increments per pick so a slow analysis can't attach to a newer photo.
|
| 22 |
+
const uploadSeq = useRef(0);
|
| 23 |
+
|
| 24 |
+
function pick(file: File) {
|
| 25 |
+
setError(null);
|
| 26 |
+
setAnalysis(null);
|
| 27 |
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
| 28 |
+
|
| 29 |
+
const url = URL.createObjectURL(file);
|
| 30 |
+
setPreviewUrl(url);
|
| 31 |
+
|
| 32 |
+
const img = new Image();
|
| 33 |
+
img.onload = () => setImageSize({ w: img.naturalWidth, h: img.naturalHeight });
|
| 34 |
+
img.src = url;
|
| 35 |
+
|
| 36 |
+
void upload(file, ++uploadSeq.current);
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
async function upload(file: File, seq: number) {
|
| 40 |
+
setIsLoading(true);
|
| 41 |
+
try {
|
| 42 |
+
const form = new FormData();
|
| 43 |
+
form.append("image", file);
|
| 44 |
+
form.append("level", level);
|
| 45 |
+
const res = await fetch("/api/vision/analyze", { method: "POST", body: form });
|
| 46 |
+
if (seq !== uploadSeq.current) return;
|
| 47 |
+
if (!res.ok) {
|
| 48 |
+
const j = await res.json().catch(() => ({}));
|
| 49 |
+
setError(j.message || j.error || "Analysis failed.");
|
| 50 |
+
return;
|
| 51 |
+
}
|
| 52 |
+
setAnalysis((await res.json()) as Analysis);
|
| 53 |
+
} catch {
|
| 54 |
+
if (seq === uploadSeq.current) setError("Network error. Please try again.");
|
| 55 |
+
} finally {
|
| 56 |
+
if (seq === uploadSeq.current) setIsLoading(false);
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
function onInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
| 61 |
+
const file = e.target.files?.[0];
|
| 62 |
+
if (file) pick(file);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
function clear() {
|
| 66 |
+
uploadSeq.current++;
|
| 67 |
+
if (previewUrl) URL.revokeObjectURL(previewUrl);
|
| 68 |
+
setPreviewUrl(null);
|
| 69 |
+
setAnalysis(null);
|
| 70 |
+
setImageSize(null);
|
| 71 |
+
setError(null);
|
| 72 |
+
setIsLoading(false);
|
| 73 |
+
if (inputRef.current) inputRef.current.value = "";
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
return (
|
| 77 |
+
<div className="space-y-8">
|
| 78 |
+
<input
|
| 79 |
+
ref={inputRef}
|
| 80 |
+
type="file"
|
| 81 |
+
accept="image/*"
|
| 82 |
+
capture="environment"
|
| 83 |
+
onChange={onInputChange}
|
| 84 |
+
className="hidden"
|
| 85 |
+
/>
|
| 86 |
+
|
| 87 |
+
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 rounded-2xl bg-white p-5 border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
| 88 |
+
<span className="text-sm font-black uppercase tracking-wide text-black">Sentences match level</span>
|
| 89 |
+
<LevelPicker value={level} onChange={setLevel} size="sm" />
|
| 90 |
+
</div>
|
| 91 |
+
|
| 92 |
+
{!previewUrl && <UploadDropzone onPick={() => inputRef.current?.click()} />}
|
| 93 |
+
|
| 94 |
+
{previewUrl && imageSize && (
|
| 95 |
+
<div className="space-y-6">
|
| 96 |
+
<div className="flex flex-col gap-4 sm:flex-row sm:items-center">
|
| 97 |
+
<button
|
| 98 |
+
onClick={() => inputRef.current?.click()}
|
| 99 |
+
className="flex items-center justify-center gap-2.5 rounded-xl bg-[#3B82F6] border-3 border-black text-white px-8 py-3 text-base font-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all uppercase tracking-wider"
|
| 100 |
+
>
|
| 101 |
+
<Camera size={20} strokeWidth={2.5} />
|
| 102 |
+
New photo
|
| 103 |
+
</button>
|
| 104 |
+
<button
|
| 105 |
+
onClick={clear}
|
| 106 |
+
className="flex items-center justify-center gap-2.5 rounded-xl bg-white border-3 border-black text-black px-8 py-3 text-base font-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[6px_6px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all uppercase tracking-wider"
|
| 107 |
+
>
|
| 108 |
+
<RotateCcw size={20} strokeWidth={2.5} />
|
| 109 |
+
Clear
|
| 110 |
+
</button>
|
| 111 |
+
</div>
|
| 112 |
+
|
| 113 |
+
<div className="relative inline-block max-w-full overflow-hidden rounded-2xl bg-white p-2 border-3 border-black shadow-[8px_8px_0px_rgba(0,0,0,1)]">
|
| 114 |
+
<div className="relative inline-block max-w-full overflow-hidden rounded-xl bg-black">
|
| 115 |
+
<img src={previewUrl} alt="Selected practice" className="block max-h-[560px] w-auto max-w-full object-contain" />
|
| 116 |
+
|
| 117 |
+
{analysis ? <PhotoMarkers objects={analysis.objects} imageSize={imageSize} /> : null}
|
| 118 |
+
|
| 119 |
+
{isLoading && (
|
| 120 |
+
<div className="absolute inset-0 flex flex-col items-center justify-center gap-4 bg-white/90 backdrop-blur-sm z-20">
|
| 121 |
+
<Wand2 size={48} strokeWidth={2.5} className="animate-bounce text-[#3B82F6]" />
|
| 122 |
+
<span className="text-lg font-black text-black uppercase tracking-wider">Analyzing image...</span>
|
| 123 |
+
</div>
|
| 124 |
+
)}
|
| 125 |
+
</div>
|
| 126 |
+
</div>
|
| 127 |
+
|
| 128 |
+
{analysis?.caption ? (
|
| 129 |
+
<div className="rounded-xl bg-[#FFD21E] border-3 border-black p-5 text-base font-black text-black shadow-[4px_4px_0px_rgba(0,0,0,1)] italic">
|
| 130 |
+
“{analysis.caption}”
|
| 131 |
+
</div>
|
| 132 |
+
) : null}
|
| 133 |
+
</div>
|
| 134 |
+
)}
|
| 135 |
+
|
| 136 |
+
{error ? (
|
| 137 |
+
<motion.div
|
| 138 |
+
initial={{ opacity: 0, y: 8 }}
|
| 139 |
+
animate={{ opacity: 1, y: 0 }}
|
| 140 |
+
className="flex items-start gap-4 rounded-xl border-3 border-black bg-[#FF8080] p-5 shadow-[4px_4px_0px_rgba(0,0,0,1)]"
|
| 141 |
+
>
|
| 142 |
+
<AlertCircle size={24} strokeWidth={2.5} className="text-black shrink-0" />
|
| 143 |
+
<div>
|
| 144 |
+
<p className="text-base font-black text-black uppercase tracking-tight">Analysis failed</p>
|
| 145 |
+
<p className="mt-1 text-sm font-bold text-black/80">{error}</p>
|
| 146 |
+
</div>
|
| 147 |
+
</motion.div>
|
| 148 |
+
) : null}
|
| 149 |
+
|
| 150 |
+
{analysis ? <AnalysisResults analysis={analysis} lang={lang} /> : null}
|
| 151 |
+
</div>
|
| 152 |
+
);
|
| 153 |
+
}
|
app/(app)/camera/PhotoMarkers.tsx
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import type { FuriSegment } from "@/lib/types/dialogue";
|
| 5 |
+
import { Furi } from "@/components/Furi";
|
| 6 |
+
|
| 7 |
+
type MarkerObject = {
|
| 8 |
+
label: string;
|
| 9 |
+
translation: string;
|
| 10 |
+
translationSegments?: FuriSegment[];
|
| 11 |
+
romanized?: string;
|
| 12 |
+
box: [number, number, number, number];
|
| 13 |
+
};
|
| 14 |
+
|
| 15 |
+
export function PhotoMarkers({
|
| 16 |
+
objects,
|
| 17 |
+
imageSize,
|
| 18 |
+
}: {
|
| 19 |
+
objects: MarkerObject[];
|
| 20 |
+
imageSize: { w: number; h: number };
|
| 21 |
+
}) {
|
| 22 |
+
const [selected, setSelected] = useState<number | null>(null);
|
| 23 |
+
|
| 24 |
+
return (
|
| 25 |
+
<>
|
| 26 |
+
{objects.map((obj, i) => {
|
| 27 |
+
const [x1, y1, x2, y2] = obj.box;
|
| 28 |
+
const isNormalized = x1 <= 1 && y1 <= 1 && x2 <= 1 && y2 <= 1;
|
| 29 |
+
const cx = (x1 + x2) / 2;
|
| 30 |
+
const cy = (y1 + y2) / 2;
|
| 31 |
+
const left = isNormalized ? `${cx * 100}%` : `${(cx / imageSize.w) * 100}%`;
|
| 32 |
+
const top = isNormalized ? `${cy * 100}%` : `${(cy / imageSize.h) * 100}%`;
|
| 33 |
+
|
| 34 |
+
const isSelected = selected === i;
|
| 35 |
+
const isDimmed = selected !== null && !isSelected;
|
| 36 |
+
const wrapperOpacity = isDimmed ? "opacity-30" : "opacity-100";
|
| 37 |
+
const wrapperZ = isSelected ? "z-20" : "z-10";
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<button
|
| 41 |
+
type="button"
|
| 42 |
+
key={`${obj.translation}-${i}`}
|
| 43 |
+
onClick={(e) => {
|
| 44 |
+
e.stopPropagation();
|
| 45 |
+
setSelected(isSelected ? null : i);
|
| 46 |
+
}}
|
| 47 |
+
className={`absolute flex -translate-x-1/2 -translate-y-1/2 flex-col items-center transition-opacity duration-200 ${wrapperOpacity} ${wrapperZ}`}
|
| 48 |
+
style={{ left, top }}
|
| 49 |
+
>
|
| 50 |
+
<span
|
| 51 |
+
className={`relative rounded-full border-2 border-black bg-[#FFD21E] transition-all duration-200 ${
|
| 52 |
+
isSelected ? "h-5 w-5 bg-[#0EA5A4] shadow-[2px_2px_0px_rgba(0,0,0,1)]" : "h-4 w-4"
|
| 53 |
+
}`}
|
| 54 |
+
/>
|
| 55 |
+
<div
|
| 56 |
+
className={`mt-2 rounded-xl border-2 border-black bg-white px-3 py-1.5 text-center shadow-[3px_3px_0px_rgba(0,0,0,1)] transition-all duration-200 ${
|
| 57 |
+
isSelected ? "scale-110 shadow-[5px_5px_0px_rgba(0,0,0,1)] bg-[#FFD21E]" : ""
|
| 58 |
+
}`}
|
| 59 |
+
>
|
| 60 |
+
<div
|
| 61 |
+
className={`whitespace-nowrap font-black text-black ${
|
| 62 |
+
isSelected ? "text-sm" : "text-xs"
|
| 63 |
+
} ${obj.translationSegments ? "has-furi" : ""}`}
|
| 64 |
+
>
|
| 65 |
+
<Furi text={obj.translation} segments={obj.translationSegments} />
|
| 66 |
+
</div>
|
| 67 |
+
{obj.romanized ? (
|
| 68 |
+
<div
|
| 69 |
+
className={`whitespace-nowrap font-bold text-gray-700 ${
|
| 70 |
+
isSelected ? "text-xs" : "text-[10px]"
|
| 71 |
+
}`}
|
| 72 |
+
>
|
| 73 |
+
{obj.romanized}
|
| 74 |
+
</div>
|
| 75 |
+
) : null}
|
| 76 |
+
</div>
|
| 77 |
+
</button>
|
| 78 |
+
);
|
| 79 |
+
})}
|
| 80 |
+
</>
|
| 81 |
+
);
|
| 82 |
+
}
|
app/(app)/camera/UploadDropzone.tsx
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { motion } from "framer-motion";
|
| 4 |
+
import { ImagePlus } from "lucide-react";
|
| 5 |
+
|
| 6 |
+
const EASE = [0.16, 1, 0.3, 1] as const;
|
| 7 |
+
|
| 8 |
+
export function UploadDropzone({ onPick }: { onPick: () => void }) {
|
| 9 |
+
return (
|
| 10 |
+
<motion.button
|
| 11 |
+
type="button"
|
| 12 |
+
initial={{ opacity: 0, y: 12 }}
|
| 13 |
+
animate={{ opacity: 1, y: 0 }}
|
| 14 |
+
transition={{ duration: 0.5, ease: EASE }}
|
| 15 |
+
onClick={onPick}
|
| 16 |
+
className="group relative flex min-h-[360px] w-full flex-col items-center justify-center rounded-2xl border-3 border-black bg-white shadow-[8px_8px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[10px_10px_0px_rgba(0,0,0,1)] active:translate-x-[2px] active:translate-y-[2px] active:shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all p-8 text-center overflow-hidden cursor-pointer"
|
| 17 |
+
>
|
| 18 |
+
<div className="relative flex flex-col items-center z-10">
|
| 19 |
+
<div className="mb-6 flex h-24 w-24 items-center justify-center rounded-2xl border-3 border-black bg-[#FFD21E] shadow-[4px_4px_0px_rgba(0,0,0,1)] group-hover:scale-105 transition-transform duration-300">
|
| 20 |
+
<ImagePlus size={48} strokeWidth={2.5} className="text-black" />
|
| 21 |
+
</div>
|
| 22 |
+
<h3 className="text-2xl font-black text-black tracking-tight mb-2 uppercase">Drop anything to upload</h3>
|
| 23 |
+
<p className="max-w-sm text-sm font-bold text-gray-700 leading-relaxed">
|
| 24 |
+
Camera or gallery images automatically become interactive vocabulary cards.
|
| 25 |
+
</p>
|
| 26 |
+
</div>
|
| 27 |
+
</motion.button>
|
| 28 |
+
);
|
| 29 |
+
}
|
app/(app)/camera/page.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getSession } from "@/lib/auth/session";
|
| 2 |
+
import type { Level } from "@/lib/languages";
|
| 3 |
+
import { CameraClient } from "./CameraClient";
|
| 4 |
+
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
| 5 |
+
|
| 6 |
+
export default async function CameraPage() {
|
| 7 |
+
const session = (await getSession())!;
|
| 8 |
+
const targetLangs = session.targetLangs ?? (session.targetLang ? [session.targetLang] : []);
|
| 9 |
+
return (
|
| 10 |
+
<section className="space-y-8 max-w-6xl mx-auto">
|
| 11 |
+
<div className="relative w-full rounded-2xl bg-[#3B82F6] text-white border-3 border-black p-6 sm:p-8 shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 12 |
+
<div className="flex flex-col gap-2">
|
| 13 |
+
<div className="min-w-0">
|
| 14 |
+
<span className="text-xs font-black uppercase tracking-wider text-black bg-white px-2.5 py-1 rounded-md border-2 border-black shadow-[1px_1px_0px_rgba(0,0,0,1)] inline-block">
|
| 15 |
+
Image practice
|
| 16 |
+
</span>
|
| 17 |
+
<h1 className="text-2xl sm:text-3xl font-black text-white tracking-tight mt-3 leading-snug">
|
| 18 |
+
Learn from a photo
|
| 19 |
+
</h1>
|
| 20 |
+
<p className="mt-2 text-base font-bold text-white/95 leading-relaxed max-w-2xl">
|
| 21 |
+
Capture objects around you and turn them into vocabulary.
|
| 22 |
+
</p>
|
| 23 |
+
</div>
|
| 24 |
+
<LanguageSwitcher
|
| 25 |
+
targetLangs={targetLangs}
|
| 26 |
+
currentLang={session.targetLang!}
|
| 27 |
+
nativeLang={session.nativeLang ?? "en"}
|
| 28 |
+
level={session.level ?? "B1"}
|
| 29 |
+
/>
|
| 30 |
+
</div>
|
| 31 |
+
</div>
|
| 32 |
+
<CameraClient defaultLevel={(session.level ?? "B1") as Level} lang={session.targetLang ?? "en"} />
|
| 33 |
+
</section>
|
| 34 |
+
);
|
| 35 |
+
}
|
| 36 |
+
|
app/(app)/camera/types.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { FuriSegment } from "@/lib/types/dialogue";
|
| 2 |
+
|
| 3 |
+
export type AnalysisObject = {
|
| 4 |
+
label: string;
|
| 5 |
+
translation: string;
|
| 6 |
+
translationSegments?: FuriSegment[];
|
| 7 |
+
romanized?: string;
|
| 8 |
+
box: [number, number, number, number];
|
| 9 |
+
score: number;
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
export type AnalysisSentence = {
|
| 13 |
+
target: string;
|
| 14 |
+
targetSegments?: FuriSegment[];
|
| 15 |
+
gloss: string;
|
| 16 |
+
romanized?: string;
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
export type Analysis = {
|
| 20 |
+
id: string;
|
| 21 |
+
caption: string;
|
| 22 |
+
objects: AnalysisObject[];
|
| 23 |
+
sentences: AnalysisSentence[];
|
| 24 |
+
imageUrl?: string;
|
| 25 |
+
};
|
app/(app)/error.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
export default function AppError({ error, reset }: { error: Error; reset: () => void }) {
|
| 4 |
+
return (
|
| 5 |
+
<section className="duo-card mx-auto max-w-md space-y-4 p-8 text-center">
|
| 6 |
+
<h1 className="text-2xl font-black text-[#3C3C3C]">Something went wrong</h1>
|
| 7 |
+
<p className="text-sm font-bold text-[#777777]">{error.message || "Please try again."}</p>
|
| 8 |
+
<button
|
| 9 |
+
onClick={reset}
|
| 10 |
+
className="btn-duo btn-duo-primary"
|
| 11 |
+
>
|
| 12 |
+
Try again
|
| 13 |
+
</button>
|
| 14 |
+
</section>
|
| 15 |
+
);
|
| 16 |
+
}
|
| 17 |
+
|
app/(app)/layout.tsx
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Link from "next/link";
|
| 2 |
+
import Image from "next/image";
|
| 3 |
+
import { redirect } from "next/navigation";
|
| 4 |
+
import { Camera, Home, Languages, User } from "lucide-react";
|
| 5 |
+
import { getSession } from "@/lib/auth/session";
|
| 6 |
+
import { BottomNavLink } from "./BottomNavLink";
|
| 7 |
+
|
| 8 |
+
export default async function AppLayout({ children }: { children: React.ReactNode }) {
|
| 9 |
+
const session = await getSession();
|
| 10 |
+
if (!session) redirect("/");
|
| 11 |
+
if (!session.targetLang || !session.level) redirect("/onboarding");
|
| 12 |
+
|
| 13 |
+
return (
|
| 14 |
+
<div className="min-h-screen bg-[#F8FBFF] text-[#4B4B4B]">
|
| 15 |
+
<header className="mx-auto flex w-full max-w-6xl items-center px-4 pt-4 sm:px-6 sm:pt-6">
|
| 16 |
+
<Link href="/phrases" className="flex items-center gap-3 text-[#3C3C3C]">
|
| 17 |
+
<Image
|
| 18 |
+
src="/logo.png"
|
| 19 |
+
alt="PraxaLing logo"
|
| 20 |
+
width={48}
|
| 21 |
+
height={48}
|
| 22 |
+
className="h-12 w-12 rounded-xl border-2 border-black object-cover shadow-[3px_3px_0px_rgba(0,0,0,1)]"
|
| 23 |
+
/>
|
| 24 |
+
<span className="text-lg font-black leading-none">PraxaLing</span>
|
| 25 |
+
</Link>
|
| 26 |
+
</header>
|
| 27 |
+
|
| 28 |
+
<main className="duo-safe-bottom mx-auto w-full max-w-6xl px-4 py-5 sm:px-6 sm:py-7 md:pb-28">
|
| 29 |
+
{children}
|
| 30 |
+
</main>
|
| 31 |
+
|
| 32 |
+
<nav className="fixed inset-x-0 bottom-0 z-50 border-t-3 border-black bg-white px-4 pb-[calc(0.75rem+env(safe-area-inset-bottom))] pt-3 shadow-[0_-4px_0_rgba(0,0,0,0.06)]">
|
| 33 |
+
<div className="mx-auto grid max-w-2xl grid-cols-4 gap-2">
|
| 34 |
+
<BottomNavLink href="/phrases" icon={<Languages size={21} strokeWidth={2.5} />} label="Phrases" />
|
| 35 |
+
<BottomNavLink href="/camera" icon={<Camera size={21} strokeWidth={2.5} />} label="Camera" />
|
| 36 |
+
<BottomNavLink href="/practice" icon={<Home size={21} strokeWidth={2.5} />} label="Practice" />
|
| 37 |
+
<BottomNavLink href="/profile" icon={<User size={21} strokeWidth={2.5} />} label="Profile" />
|
| 38 |
+
</div>
|
| 39 |
+
</nav>
|
| 40 |
+
</div>
|
| 41 |
+
);
|
| 42 |
+
}
|
app/(app)/phrases/PhraseResult/EmptyState.tsx
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { PenLine } from "lucide-react";
|
| 2 |
+
|
| 3 |
+
export function EmptyState({ isPending = false }: { isPending?: boolean }) {
|
| 4 |
+
return (
|
| 5 |
+
<div className="rounded-2xl bg-white p-8 text-center sm:p-12 border-3 border-black shadow-[8px_8px_0px_rgba(0,0,0,1)] relative overflow-hidden">
|
| 6 |
+
<div className="mx-auto flex h-20 w-20 items-center justify-center rounded-2xl bg-[#FFD21E] border-3 border-black text-black shadow-[4px_4px_0px_rgba(0,0,0,1)] rotate-3">
|
| 7 |
+
<PenLine size={32} strokeWidth={3} className="-rotate-3" />
|
| 8 |
+
</div>
|
| 9 |
+
{isPending ? (
|
| 10 |
+
<div className="mt-8 space-y-4">
|
| 11 |
+
<div className="text-xl sm:text-2xl font-black text-black tracking-tight uppercase leading-none">
|
| 12 |
+
Building your mini lesson...
|
| 13 |
+
</div>
|
| 14 |
+
<div className="mx-auto h-6 w-full max-w-md overflow-hidden rounded-full border-3 border-black bg-white shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
| 15 |
+
<div className="phrases-progress-bar h-full bg-[#0EA5A4]" />
|
| 16 |
+
</div>
|
| 17 |
+
</div>
|
| 18 |
+
) : (
|
| 19 |
+
<h2 className="mt-8 text-2xl sm:text-3xl font-black text-black tracking-tight uppercase leading-none">
|
| 20 |
+
Enter a phrase to build a mini lesson
|
| 21 |
+
</h2>
|
| 22 |
+
)}
|
| 23 |
+
<style>{`
|
| 24 |
+
@keyframes phrases-progress-slide {
|
| 25 |
+
0% { transform: translateX(-100%); }
|
| 26 |
+
100% { transform: translateX(300%); }
|
| 27 |
+
}
|
| 28 |
+
.phrases-progress-bar {
|
| 29 |
+
width: 33%;
|
| 30 |
+
animation: phrases-progress-slide 1.4s ease-in-out infinite;
|
| 31 |
+
}
|
| 32 |
+
`}</style>
|
| 33 |
+
</div>
|
| 34 |
+
);
|
| 35 |
+
}
|
app/(app)/phrases/PhraseResult/HeaderCard.tsx
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { FuriSegment } from "@/lib/types/dialogue";
|
| 2 |
+
import { Furi } from "@/components/Furi";
|
| 3 |
+
import { TTSButton } from "@/components/TTSButton";
|
| 4 |
+
|
| 5 |
+
export function HeaderCard({
|
| 6 |
+
input,
|
| 7 |
+
inputSegments,
|
| 8 |
+
partOfSpeech,
|
| 9 |
+
translation,
|
| 10 |
+
lang,
|
| 11 |
+
}: {
|
| 12 |
+
input: string;
|
| 13 |
+
inputSegments?: FuriSegment[];
|
| 14 |
+
partOfSpeech: string;
|
| 15 |
+
translation: string;
|
| 16 |
+
lang: string;
|
| 17 |
+
}) {
|
| 18 |
+
return (
|
| 19 |
+
<div className="rounded-2xl overflow-hidden bg-[#FFD21E] border-3 border-black p-6 sm:p-8 text-black shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 20 |
+
<div className="inline-block rounded-md bg-white border-2 border-black px-2.5 py-1 text-xs font-black uppercase tracking-wider text-black shadow-[1px_1px_0px_rgba(0,0,0,1)] mb-3">{partOfSpeech}</div>
|
| 21 |
+
<div className="flex items-center gap-3">
|
| 22 |
+
<h2 className={`text-3xl font-black tracking-tight sm:text-4xl text-black ${inputSegments ? "has-furi" : ""}`}>
|
| 23 |
+
<Furi text={input} segments={inputSegments} />
|
| 24 |
+
</h2>
|
| 25 |
+
<TTSButton
|
| 26 |
+
text={input}
|
| 27 |
+
lang={lang}
|
| 28 |
+
/>
|
| 29 |
+
</div>
|
| 30 |
+
<p className="mt-3 text-base font-black text-gray-800 sm:text-lg">{translation}</p>
|
| 31 |
+
</div>
|
| 32 |
+
);
|
| 33 |
+
}
|
app/(app)/phrases/PhraseResult/KanjiCard.tsx
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { KanjiInfo } from "@/lib/hf/phrases";
|
| 2 |
+
import { Furi } from "@/components/Furi";
|
| 3 |
+
|
| 4 |
+
export function KanjiCard({ kanji }: { kanji: KanjiInfo }) {
|
| 5 |
+
return (
|
| 6 |
+
<div className="rounded-xl bg-white p-5 border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 7 |
+
<div className="flex items-baseline gap-3">
|
| 8 |
+
<div className="text-4xl font-black leading-none text-black">{kanji.kanji}</div>
|
| 9 |
+
<div className="text-sm font-bold text-gray-700">{kanji.meaning}</div>
|
| 10 |
+
</div>
|
| 11 |
+
<dl className="mt-4 space-y-3 text-base">
|
| 12 |
+
<div className="flex items-center gap-2.5">
|
| 13 |
+
<dt className="w-12 shrink-0 text-xs font-black uppercase tracking-wider text-black bg-[#FFD21E] border-2 border-black rounded py-1 text-center shadow-[2px_2px_0px_rgba(0,0,0,1)]">On</dt>
|
| 14 |
+
<dd className="text-black font-black text-lg">
|
| 15 |
+
{kanji.onyomi.length ? kanji.onyomi.join("、") : <span className="text-gray-300">—</span>}
|
| 16 |
+
</dd>
|
| 17 |
+
</div>
|
| 18 |
+
<div className="flex items-center gap-2.5">
|
| 19 |
+
<dt className="w-12 shrink-0 text-xs font-black uppercase tracking-wider text-white bg-[#0EA5A4] border-2 border-black rounded py-1 text-center shadow-[2px_2px_0px_rgba(0,0,0,1)]">Kun</dt>
|
| 20 |
+
<dd className="text-black font-black text-lg">
|
| 21 |
+
{kanji.kunyomi.length ? kanji.kunyomi.join("、") : <span className="text-gray-300">—</span>}
|
| 22 |
+
</dd>
|
| 23 |
+
</div>
|
| 24 |
+
</dl>
|
| 25 |
+
{kanji.exampleWord ? (
|
| 26 |
+
<div className="mt-4 inline-flex rounded-lg bg-[#E6FBFA] border-2 border-black px-4 py-2 text-base font-black text-black shadow-[3px_3px_0px_rgba(0,0,0,1)] has-furi">
|
| 27 |
+
<Furi
|
| 28 |
+
text={kanji.exampleWord}
|
| 29 |
+
segments={
|
| 30 |
+
kanji.exampleWordReading
|
| 31 |
+
? [{ text: kanji.exampleWord, reading: kanji.exampleWordReading }]
|
| 32 |
+
: undefined
|
| 33 |
+
}
|
| 34 |
+
/>
|
| 35 |
+
</div>
|
| 36 |
+
) : null}
|
| 37 |
+
</div>
|
| 38 |
+
);
|
| 39 |
+
}
|
app/(app)/phrases/PhraseResult/RelatedWordCard.tsx
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { RelatedWord } from "@/lib/hf/phrases";
|
| 2 |
+
import { Furi } from "@/components/Furi";
|
| 3 |
+
import { TTSButton } from "@/components/TTSButton";
|
| 4 |
+
|
| 5 |
+
export function RelatedWordCard({ word, lang }: { word: RelatedWord; lang: string }) {
|
| 6 |
+
return (
|
| 7 |
+
<div className="rounded-xl bg-white p-5 border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 8 |
+
<div className="flex items-center justify-between gap-2">
|
| 9 |
+
<div className={`text-xl font-black text-black ${word.wordSegments ? "has-furi" : ""}`}>
|
| 10 |
+
<Furi text={word.word} segments={word.wordSegments} />
|
| 11 |
+
</div>
|
| 12 |
+
<div className="flex items-center gap-2">
|
| 13 |
+
{word.partOfSpeech ? (
|
| 14 |
+
<span className="text-[10px] font-black uppercase tracking-wider text-black bg-[#FFD21E] border border-black rounded px-1.5 py-0.5 shadow-[1px_1px_0px_rgba(0,0,0,1)]">
|
| 15 |
+
{word.partOfSpeech}
|
| 16 |
+
</span>
|
| 17 |
+
) : null}
|
| 18 |
+
<TTSButton text={word.word} lang={lang} />
|
| 19 |
+
</div>
|
| 20 |
+
</div>
|
| 21 |
+
<div className="mt-3 text-base font-bold text-gray-700">{word.translation}</div>
|
| 22 |
+
{word.note ? <p className="mt-2 text-sm font-black text-gray-500">{word.note}</p> : null}
|
| 23 |
+
</div>
|
| 24 |
+
);
|
| 25 |
+
}
|
app/(app)/phrases/PhraseResult/Section.tsx
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export function Section({
|
| 2 |
+
title,
|
| 3 |
+
icon,
|
| 4 |
+
children,
|
| 5 |
+
}: {
|
| 6 |
+
title: string;
|
| 7 |
+
icon: React.ReactNode;
|
| 8 |
+
children: React.ReactNode;
|
| 9 |
+
}) {
|
| 10 |
+
return (
|
| 11 |
+
<section>
|
| 12 |
+
<div className="mb-4 flex items-center gap-2.5 px-1">
|
| 13 |
+
<span className="text-black font-black">{icon}</span>
|
| 14 |
+
<h2 className="text-xl font-black text-black tracking-tight sm:text-2xl">{title}</h2>
|
| 15 |
+
</div>
|
| 16 |
+
{children}
|
| 17 |
+
</section>
|
| 18 |
+
);
|
| 19 |
+
}
|
app/(app)/phrases/PhraseResult/SentenceCard.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { PhraseSentence } from "@/lib/hf/phrases";
|
| 2 |
+
import { Furi } from "@/components/Furi";
|
| 3 |
+
import { TTSButton } from "@/components/TTSButton";
|
| 4 |
+
|
| 5 |
+
export function SentenceCard({ sentence, lang }: { sentence: PhraseSentence; lang: string }) {
|
| 6 |
+
return (
|
| 7 |
+
<div className="rounded-xl bg-white p-5 border-3 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] transition-all hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 8 |
+
<div className="flex items-start justify-between gap-3">
|
| 9 |
+
<div className={`text-lg font-black text-black ${sentence.targetSegments ? "has-furi" : ""}`}>
|
| 10 |
+
<Furi text={sentence.target} segments={sentence.targetSegments} />
|
| 11 |
+
</div>
|
| 12 |
+
<TTSButton text={sentence.target} lang={lang} />
|
| 13 |
+
</div>
|
| 14 |
+
<div className="mt-2 text-sm font-bold text-gray-700">{sentence.translation}</div>
|
| 15 |
+
{sentence.note ? (
|
| 16 |
+
<div className="mt-3 inline-block rounded-md bg-[#FFF7D6] border-2 border-black px-2.5 py-1 text-[10px] font-black uppercase tracking-wider text-black shadow-[1px_1px_0px_rgba(0,0,0,1)]">{sentence.note}</div>
|
| 17 |
+
) : null}
|
| 18 |
+
</div>
|
| 19 |
+
);
|
| 20 |
+
}
|
app/(app)/phrases/PhraseResult/index.tsx
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { BookOpen, Languages, Lightbulb, ListChecks, MessageSquareText } from "lucide-react";
|
| 2 |
+
import type { PhraseAnalysis } from "@/lib/hf/phrases";
|
| 3 |
+
import { Section } from "./Section";
|
| 4 |
+
import { HeaderCard } from "./HeaderCard";
|
| 5 |
+
import { SentenceCard } from "./SentenceCard";
|
| 6 |
+
import { KanjiCard } from "./KanjiCard";
|
| 7 |
+
import { RelatedWordCard } from "./RelatedWordCard";
|
| 8 |
+
|
| 9 |
+
export function PhraseResult({ result, lang }: { result: PhraseAnalysis; lang: string }) {
|
| 10 |
+
return (
|
| 11 |
+
<div className="space-y-6">
|
| 12 |
+
<HeaderCard
|
| 13 |
+
input={result.input}
|
| 14 |
+
inputSegments={result.inputSegments}
|
| 15 |
+
partOfSpeech={result.partOfSpeech}
|
| 16 |
+
translation={result.translation}
|
| 17 |
+
lang={lang}
|
| 18 |
+
/>
|
| 19 |
+
|
| 20 |
+
<Section title="Example sentences" icon={<MessageSquareText size={22} />}>
|
| 21 |
+
<div className="grid gap-3">
|
| 22 |
+
{result.sentences.map((sentence, index) => (
|
| 23 |
+
<SentenceCard key={`${sentence.target}-${index}`} sentence={sentence} lang={lang} />
|
| 24 |
+
))}
|
| 25 |
+
</div>
|
| 26 |
+
</Section>
|
| 27 |
+
|
| 28 |
+
{result.kanjiInfo.length > 0 ? (
|
| 29 |
+
<Section title="Kanji breakdown" icon={<Languages size={22} />}>
|
| 30 |
+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
| 31 |
+
{result.kanjiInfo.map((kanji, index) => (
|
| 32 |
+
<KanjiCard key={`${kanji.kanji}-${index}`} kanji={kanji} />
|
| 33 |
+
))}
|
| 34 |
+
</div>
|
| 35 |
+
</Section>
|
| 36 |
+
) : null}
|
| 37 |
+
|
| 38 |
+
{result.relatedWords.length > 0 ? (
|
| 39 |
+
<Section title="Related words" icon={<BookOpen size={22} strokeWidth={2.5} />}>
|
| 40 |
+
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
|
| 41 |
+
{result.relatedWords.map((word, index) => (
|
| 42 |
+
<RelatedWordCard key={`${word.word}-${index}`} word={word} lang={lang} />
|
| 43 |
+
))}
|
| 44 |
+
</div>
|
| 45 |
+
</Section>
|
| 46 |
+
) : null}
|
| 47 |
+
|
| 48 |
+
<div className="grid gap-6 lg:grid-cols-2">
|
| 49 |
+
<Section title="Breakdown" icon={<ListChecks size={22} strokeWidth={2.5} />}>
|
| 50 |
+
<div className="space-y-3">
|
| 51 |
+
{result.breakdown.map((item, index) => (
|
| 52 |
+
<div key={`${item.part}-${index}`} className="rounded-xl bg-white border-2 border-black p-4 transition-all shadow-[3px_3px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
| 53 |
+
<div className="text-base font-black text-black">{item.part}</div>
|
| 54 |
+
<p className="mt-1 text-sm font-bold leading-relaxed text-gray-700">{item.explanation}</p>
|
| 55 |
+
</div>
|
| 56 |
+
))}
|
| 57 |
+
</div>
|
| 58 |
+
</Section>
|
| 59 |
+
|
| 60 |
+
<Section title="Tips and verb notes" icon={<Lightbulb size={22} strokeWidth={2.5} />}>
|
| 61 |
+
{result.verbInfo ? (
|
| 62 |
+
<div className="mb-4 rounded-xl border-3 border-black bg-[#FFD21E] p-4 shadow-[3px_3px_0px_rgba(0,0,0,1)] text-black">
|
| 63 |
+
<div className="text-xs font-black uppercase tracking-wider text-black">Verb info</div>
|
| 64 |
+
<p className="mt-1 text-sm font-black leading-relaxed text-black">{result.verbInfo}</p>
|
| 65 |
+
</div>
|
| 66 |
+
) : null}
|
| 67 |
+
<ul className="space-y-3">
|
| 68 |
+
{result.tips.map((tip, index) => (
|
| 69 |
+
<li key={`${tip}-${index}`} className="rounded-xl bg-white border-2 border-black p-4 text-sm font-bold leading-relaxed text-black transition-all shadow-[3px_3px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[4px_4px_0px_rgba(0,0,0,1)]">
|
| 70 |
+
{tip}
|
| 71 |
+
</li>
|
| 72 |
+
))}
|
| 73 |
+
</ul>
|
| 74 |
+
</Section>
|
| 75 |
+
</div>
|
| 76 |
+
</div>
|
| 77 |
+
);
|
| 78 |
+
}
|
app/(app)/phrases/PhrasesClient.tsx
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import type { PhraseAnalysis } from "@/lib/hf/phrases";
|
| 5 |
+
import type { Level } from "@/lib/languages";
|
| 6 |
+
import { LevelPicker } from "@/components/LevelPicker";
|
| 7 |
+
import { PhraseResult } from "./PhraseResult";
|
| 8 |
+
import { EmptyState } from "./PhraseResult/EmptyState";
|
| 9 |
+
|
| 10 |
+
const examples = ["Car", "Dog", "Teacher", "finding lost documents"];
|
| 11 |
+
|
| 12 |
+
export function PhrasesClient({ defaultLevel, lang }: { defaultLevel: Level; lang: string }) {
|
| 13 |
+
const [text, setText] = useState("");
|
| 14 |
+
const [result, setResult] = useState<PhraseAnalysis | null>(null);
|
| 15 |
+
const [error, setError] = useState<string | null>(null);
|
| 16 |
+
const [isPending, setIsPending] = useState(false);
|
| 17 |
+
const [level, setLevel] = useState<Level>(defaultLevel);
|
| 18 |
+
|
| 19 |
+
async function analyze(value = text) {
|
| 20 |
+
if (isPending) return;
|
| 21 |
+
const phrase = value.trim();
|
| 22 |
+
if (!phrase) {
|
| 23 |
+
setError("Enter a phrase first.");
|
| 24 |
+
return;
|
| 25 |
+
}
|
| 26 |
+
setError(null);
|
| 27 |
+
|
| 28 |
+
setIsPending(true);
|
| 29 |
+
try {
|
| 30 |
+
const res = await fetch("/api/phrases", {
|
| 31 |
+
method: "POST",
|
| 32 |
+
headers: { "content-type": "application/json" },
|
| 33 |
+
body: JSON.stringify({ text: phrase, level }),
|
| 34 |
+
});
|
| 35 |
+
if (!res.ok) {
|
| 36 |
+
const data = await res.json().catch(() => ({}));
|
| 37 |
+
setError(data.message || data.error || "Could not analyze this phrase.");
|
| 38 |
+
return;
|
| 39 |
+
}
|
| 40 |
+
setResult((await res.json()) as PhraseAnalysis);
|
| 41 |
+
} catch {
|
| 42 |
+
setError("Could not analyze this phrase.");
|
| 43 |
+
} finally {
|
| 44 |
+
setIsPending(false);
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<div className="space-y-8">
|
| 50 |
+
<div className="rounded-3xl bg-white p-8 sm:p-10 border-4 border-black shadow-[10px_10px_0px_rgba(0,0,0,1)] transition-all">
|
| 51 |
+
<div className="flex flex-wrap items-center justify-between gap-4">
|
| 52 |
+
<label className="text-base font-black uppercase tracking-widest text-black" htmlFor="phrase-input">
|
| 53 |
+
Phrase or word
|
| 54 |
+
</label>
|
| 55 |
+
<LevelPicker value={level} onChange={setLevel} size="sm" label="Level" />
|
| 56 |
+
</div>
|
| 57 |
+
<div className="mt-6 flex flex-col gap-6 sm:flex-row sm:items-stretch">
|
| 58 |
+
<input
|
| 59 |
+
id="phrase-input"
|
| 60 |
+
value={text}
|
| 61 |
+
onChange={(event) => setText(event.target.value)}
|
| 62 |
+
onKeyDown={(event) => event.key === "Enter" && analyze()}
|
| 63 |
+
placeholder="Type a phrase, word, or verb..."
|
| 64 |
+
className="h-20 w-full sm:flex-1 rounded-2xl border-4 border-black bg-white px-8 text-2xl font-black text-black outline-none transition-all focus:translate-x-[-2px] focus:translate-y-[-2px] focus:shadow-[6px_6px_0px_rgba(0,0,0,1)] placeholder:text-xl placeholder:font-bold placeholder:text-gray-400 shadow-[4px_4px_0px_rgba(0,0,0,1)]"
|
| 65 |
+
/>
|
| 66 |
+
<button
|
| 67 |
+
onClick={() => analyze()}
|
| 68 |
+
disabled={isPending}
|
| 69 |
+
className="flex h-20 w-full sm:w-auto sm:px-12 items-center justify-center rounded-2xl bg-[#0EA5A4] border-4 border-black text-xl font-black text-white shadow-[6px_6px_0px_rgba(0,0,0,1)] hover:translate-x-[-3px] hover:translate-y-[-3px] hover:shadow-[9px_9px_0px_rgba(0,0,0,1)] active:translate-x-[2px] active:translate-y-[2px] active:shadow-[2px_2px_0px_rgba(0,0,0,1)] transition-all disabled:opacity-50 uppercase tracking-widest shrink-0"
|
| 70 |
+
>
|
| 71 |
+
{isPending ? "Analyzing..." : "Analyze"}
|
| 72 |
+
</button>
|
| 73 |
+
</div>
|
| 74 |
+
{!text && (
|
| 75 |
+
<div className="mt-6 flex flex-wrap gap-3">
|
| 76 |
+
{examples.map((example) => (
|
| 77 |
+
<button
|
| 78 |
+
key={example}
|
| 79 |
+
onClick={() => {
|
| 80 |
+
setText(example);
|
| 81 |
+
analyze(example);
|
| 82 |
+
}}
|
| 83 |
+
className="rounded-xl bg-[#FFD21E] border-2 border-black px-5 py-2.5 text-sm font-black text-black shadow-[3px_3px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all uppercase tracking-tight"
|
| 84 |
+
>
|
| 85 |
+
{example}
|
| 86 |
+
</button>
|
| 87 |
+
))}
|
| 88 |
+
</div>
|
| 89 |
+
)}
|
| 90 |
+
{error ? (
|
| 91 |
+
<div className="mt-6 rounded-2xl border-4 border-black bg-[#FF8080] p-6 text-base font-black text-black shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 92 |
+
{error}
|
| 93 |
+
</div>
|
| 94 |
+
) : null}
|
| 95 |
+
</div>
|
| 96 |
+
|
| 97 |
+
{isPending ? <EmptyState isPending /> : result ? <PhraseResult result={result} lang={lang} /> : <EmptyState />}
|
| 98 |
+
</div>
|
| 99 |
+
);
|
| 100 |
+
}
|
app/(app)/phrases/page.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { getSession } from "@/lib/auth/session";
|
| 2 |
+
import { languageName, type Level } from "@/lib/languages";
|
| 3 |
+
import { PhrasesClient } from "./PhrasesClient";
|
| 4 |
+
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
| 5 |
+
|
| 6 |
+
export default async function PhrasesPage() {
|
| 7 |
+
const session = (await getSession())!;
|
| 8 |
+
const targetLangs = session.targetLangs ?? (session.targetLang ? [session.targetLang] : []);
|
| 9 |
+
|
| 10 |
+
return (
|
| 11 |
+
<section className="space-y-8 max-w-6xl mx-auto">
|
| 12 |
+
<div className="relative w-full rounded-2xl bg-[#FF6B6B] text-white border-3 border-black p-6 sm:p-8 shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 13 |
+
<div className="flex flex-col gap-2">
|
| 14 |
+
<div className="min-w-0">
|
| 15 |
+
<span className="text-xs font-black uppercase tracking-wider text-black bg-white px-2.5 py-1 rounded-md border-2 border-black shadow-[1px_1px_0px_rgba(0,0,0,1)] inline-block">
|
| 16 |
+
Phrase coach
|
| 17 |
+
</span>
|
| 18 |
+
<h1 className="text-2xl sm:text-3xl font-black text-white tracking-tight mt-3 leading-snug">
|
| 19 |
+
Break down phrases
|
| 20 |
+
</h1>
|
| 21 |
+
<p className="mt-2 text-base font-bold text-white/95 leading-relaxed max-w-2xl">
|
| 22 |
+
Master {languageName(session.targetLang!)} with deep grammar insights, accurate translations, and contextual usage tips.
|
| 23 |
+
</p>
|
| 24 |
+
</div>
|
| 25 |
+
<LanguageSwitcher
|
| 26 |
+
targetLangs={targetLangs}
|
| 27 |
+
currentLang={session.targetLang!}
|
| 28 |
+
nativeLang={session.nativeLang ?? "en"}
|
| 29 |
+
level={session.level ?? "B1"}
|
| 30 |
+
/>
|
| 31 |
+
</div>
|
| 32 |
+
</div>
|
| 33 |
+
<PhrasesClient defaultLevel={(session.level ?? "B1") as Level} lang={session.targetLang ?? "en"} />
|
| 34 |
+
</section>
|
| 35 |
+
);
|
| 36 |
+
}
|
app/(app)/practice/CustomTopicCard.tsx
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { LevelPicker } from "@/components/LevelPicker";
|
| 4 |
+
import type { Level } from "@/lib/languages";
|
| 5 |
+
import { Wand2, ArrowRight } from "lucide-react";
|
| 6 |
+
|
| 7 |
+
export function CustomTopicCard({
|
| 8 |
+
level,
|
| 9 |
+
onLevelChange,
|
| 10 |
+
customTopic,
|
| 11 |
+
onCustomTopicChange,
|
| 12 |
+
onGenerate,
|
| 13 |
+
pending,
|
| 14 |
+
disabled,
|
| 15 |
+
error,
|
| 16 |
+
}: {
|
| 17 |
+
level: Level;
|
| 18 |
+
onLevelChange: (level: Level) => void;
|
| 19 |
+
customTopic: string;
|
| 20 |
+
onCustomTopicChange: (value: string) => void;
|
| 21 |
+
onGenerate: () => void;
|
| 22 |
+
pending: boolean;
|
| 23 |
+
disabled: boolean;
|
| 24 |
+
error: string | null;
|
| 25 |
+
}) {
|
| 26 |
+
return (
|
| 27 |
+
<div className="w-full space-y-3">
|
| 28 |
+
<div className="flex flex-wrap items-center justify-between gap-3 px-1">
|
| 29 |
+
<div className="text-sm font-black text-black tracking-wide uppercase flex items-center gap-2">
|
| 30 |
+
<Wand2 size={20} strokeWidth={2.5} />
|
| 31 |
+
Build your own
|
| 32 |
+
</div>
|
| 33 |
+
<LevelPicker value={level} onChange={onLevelChange} size="sm" label="CEFR" />
|
| 34 |
+
</div>
|
| 35 |
+
|
| 36 |
+
<div className="relative w-full">
|
| 37 |
+
<textarea
|
| 38 |
+
value={customTopic}
|
| 39 |
+
onChange={(event) => onCustomTopicChange(event.target.value)}
|
| 40 |
+
placeholder="Describe a situation, e.g. returning a damaged phone at a shop..."
|
| 41 |
+
rows={2}
|
| 42 |
+
disabled={disabled && !pending}
|
| 43 |
+
className="w-full resize-none rounded-2xl border-3 border-black bg-white shadow-[4px_4px_0px_rgba(0,0,0,1)] pl-4 pr-16 py-4 text-base font-bold leading-relaxed text-black outline-none transition-all focus:translate-x-[-1px] focus:translate-y-[-1px] focus:shadow-[5px_5px_0px_rgba(0,0,0,1)] placeholder:text-gray-500"
|
| 44 |
+
style={{ minHeight: '70px' }}
|
| 45 |
+
/>
|
| 46 |
+
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center">
|
| 47 |
+
<button
|
| 48 |
+
onClick={onGenerate}
|
| 49 |
+
disabled={disabled || !customTopic.trim()}
|
| 50 |
+
className={`flex h-11 w-11 items-center justify-center rounded-xl border-2 border-black font-black transition-all ${
|
| 51 |
+
!customTopic.trim() || disabled
|
| 52 |
+
? "bg-gray-100 text-gray-400 shadow-[1px_1px_0px_rgba(0,0,0,1)]"
|
| 53 |
+
: "bg-[#0EA5A4] text-white shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[-1px] hover:translate-y-[-1px] hover:shadow-[3px_3px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)]"
|
| 54 |
+
}`}
|
| 55 |
+
>
|
| 56 |
+
{pending ? (
|
| 57 |
+
<div className="h-5 w-5 rounded-full border-2 border-white border-t-transparent animate-spin" />
|
| 58 |
+
) : (
|
| 59 |
+
<ArrowRight size={20} strokeWidth={3} />
|
| 60 |
+
)}
|
| 61 |
+
</button>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
{error ? (
|
| 66 |
+
<div className="rounded-xl border-2 border-black bg-[#FF8080] px-3 py-2 text-xs font-black text-black shadow-[2px_2px_0px_rgba(0,0,0,1)]">
|
| 67 |
+
{error}
|
| 68 |
+
</div>
|
| 69 |
+
) : null}
|
| 70 |
+
</div>
|
| 71 |
+
);
|
| 72 |
+
}
|
app/(app)/practice/DialoguePracticeClient.tsx
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import type { Dialogue } from "@/lib/types/dialogue";
|
| 5 |
+
import type { Level } from "@/lib/languages";
|
| 6 |
+
import { CustomTopicCard } from "./CustomTopicCard";
|
| 7 |
+
import { ScenarioLibrary } from "./ScenarioLibrary";
|
| 8 |
+
import { DialogueResultView } from "./DialogueResultView";
|
| 9 |
+
import { GeneratingDialogueProgress } from "./GeneratingDialogueProgress";
|
| 10 |
+
|
| 11 |
+
export function DialoguePracticeClient({ defaultLevel }: { defaultLevel: Level }) {
|
| 12 |
+
const [customTopic, setCustomTopic] = useState("");
|
| 13 |
+
const [error, setError] = useState<string | null>(null);
|
| 14 |
+
const [activeScenario, setActiveScenario] = useState<string | null>(null);
|
| 15 |
+
const [generatedDialogue, setGeneratedDialogue] = useState<Dialogue | null>(null);
|
| 16 |
+
const [level, setLevel] = useState<Level>(defaultLevel);
|
| 17 |
+
|
| 18 |
+
async function generate(scenario: string, label: string) {
|
| 19 |
+
if (activeScenario) return;
|
| 20 |
+
setError(null);
|
| 21 |
+
setActiveScenario(label);
|
| 22 |
+
|
| 23 |
+
try {
|
| 24 |
+
const res = await fetch("/api/dialogues", {
|
| 25 |
+
method: "POST",
|
| 26 |
+
headers: { "content-type": "application/json" },
|
| 27 |
+
body: JSON.stringify({ scenario, level }),
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
if (!res.ok) {
|
| 31 |
+
const data = await res.json().catch(() => ({}));
|
| 32 |
+
setError(data.message || data.error || "Could not generate this dialogue.");
|
| 33 |
+
setActiveScenario(null);
|
| 34 |
+
return;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
setGeneratedDialogue((await res.json()) as Dialogue);
|
| 38 |
+
} catch {
|
| 39 |
+
setError("Could not generate this dialogue.");
|
| 40 |
+
setActiveScenario(null);
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function generateCustom() {
|
| 45 |
+
const topic = customTopic.trim();
|
| 46 |
+
if (!topic) {
|
| 47 |
+
setError("Enter a situation first.");
|
| 48 |
+
return;
|
| 49 |
+
}
|
| 50 |
+
generate(topic, "Custom topic");
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
if (generatedDialogue) {
|
| 54 |
+
return (
|
| 55 |
+
<DialogueResultView
|
| 56 |
+
dialogue={generatedDialogue}
|
| 57 |
+
onBack={() => {
|
| 58 |
+
setGeneratedDialogue(null);
|
| 59 |
+
setActiveScenario(null);
|
| 60 |
+
}}
|
| 61 |
+
/>
|
| 62 |
+
);
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
if (activeScenario) {
|
| 66 |
+
return <GeneratingDialogueProgress scenario={activeScenario} />;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
return (
|
| 70 |
+
<section className="space-y-6">
|
| 71 |
+
<CustomTopicCard
|
| 72 |
+
level={level}
|
| 73 |
+
onLevelChange={setLevel}
|
| 74 |
+
customTopic={customTopic}
|
| 75 |
+
onCustomTopicChange={setCustomTopic}
|
| 76 |
+
onGenerate={generateCustom}
|
| 77 |
+
pending={activeScenario === "Custom topic"}
|
| 78 |
+
disabled={activeScenario !== null}
|
| 79 |
+
error={error}
|
| 80 |
+
/>
|
| 81 |
+
<ScenarioLibrary activeScenario={activeScenario} onGenerate={generate} />
|
| 82 |
+
</section>
|
| 83 |
+
);
|
| 84 |
+
}
|
app/(app)/practice/DialogueResultView.tsx
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { Dialogue } from "@/lib/types/dialogue";
|
| 2 |
+
import { DialogueClient } from "./dialogues/[id]/DialogueClient";
|
| 3 |
+
import { ArrowLeft } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function DialogueResultView({
|
| 6 |
+
dialogue,
|
| 7 |
+
onBack,
|
| 8 |
+
}: {
|
| 9 |
+
dialogue: Dialogue;
|
| 10 |
+
onBack: () => void;
|
| 11 |
+
}) {
|
| 12 |
+
return (
|
| 13 |
+
<section className="mx-auto max-w-4xl space-y-6">
|
| 14 |
+
<button
|
| 15 |
+
onClick={onBack}
|
| 16 |
+
className="inline-flex items-center gap-2.5 px-5 py-2.5 rounded-xl border-2 border-black bg-white text-black font-black text-xs uppercase tracking-wider shadow-[2px_2px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[5px_5px_0px_rgba(0,0,0,1)] active:translate-x-[1px] active:translate-y-[1px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all"
|
| 17 |
+
>
|
| 18 |
+
<ArrowLeft size={18} strokeWidth={3} />
|
| 19 |
+
Back to scenarios
|
| 20 |
+
</button>
|
| 21 |
+
<div className="rounded-2xl overflow-hidden bg-[#FFD21E] border-3 border-black p-6 sm:p-8 text-black shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 22 |
+
<div className="inline-block rounded-md bg-white border-2 border-black px-2.5 py-1 text-xs font-black uppercase tracking-wider text-black shadow-[1px_1px_0px_rgba(0,0,0,1)] mb-3">
|
| 23 |
+
{dialogue.targetLang.toUpperCase()} / {dialogue.level}
|
| 24 |
+
</div>
|
| 25 |
+
<h1 className="text-2xl sm:text-4xl font-black tracking-tight text-black leading-snug uppercase">{dialogue.title}</h1>
|
| 26 |
+
</div>
|
| 27 |
+
<DialogueClient dialogue={dialogue} />
|
| 28 |
+
</section>
|
| 29 |
+
);
|
| 30 |
+
}
|
app/(app)/practice/GeneratingDialogueProgress.tsx
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useEffect, useState } from "react";
|
| 2 |
+
import { Wand2, PenLine, Globe, Settings2, BrainCircuit, type LucideIcon } from "lucide-react";
|
| 3 |
+
import { StageItem } from "./StageItem";
|
| 4 |
+
|
| 5 |
+
type Stage = {
|
| 6 |
+
icon: LucideIcon;
|
| 7 |
+
label: string;
|
| 8 |
+
description: string;
|
| 9 |
+
};
|
| 10 |
+
|
| 11 |
+
const STAGES: Stage[] = [
|
| 12 |
+
{
|
| 13 |
+
icon: Wand2,
|
| 14 |
+
label: "Setting the scene",
|
| 15 |
+
description: "Crafting a believable situation around your topic.",
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
icon: PenLine,
|
| 19 |
+
label: "Writing the dialogue",
|
| 20 |
+
description: "Generating natural turn-by-turn conversation.",
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
icon: Globe,
|
| 24 |
+
label: "Adding translations",
|
| 25 |
+
description: "Aligning each line with your native language.",
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
icon: Settings2,
|
| 29 |
+
label: "Polishing details",
|
| 30 |
+
description: "Tuning vocabulary to your level and style.",
|
| 31 |
+
},
|
| 32 |
+
];
|
| 33 |
+
|
| 34 |
+
const STAGE_DURATIONS_MS = [2800, 4200, 5500, 7000];
|
| 35 |
+
|
| 36 |
+
export function GeneratingDialogueProgress({ scenario }: { scenario: string }) {
|
| 37 |
+
const [stageIndex, setStageIndex] = useState(0);
|
| 38 |
+
const [progress, setProgress] = useState(4);
|
| 39 |
+
|
| 40 |
+
useEffect(() => {
|
| 41 |
+
const timers: ReturnType<typeof setTimeout>[] = [];
|
| 42 |
+
let elapsed = 0;
|
| 43 |
+
STAGE_DURATIONS_MS.forEach((duration, i) => {
|
| 44 |
+
elapsed += duration;
|
| 45 |
+
if (i < STAGES.length - 1) {
|
| 46 |
+
timers.push(setTimeout(() => setStageIndex(i + 1), elapsed));
|
| 47 |
+
}
|
| 48 |
+
});
|
| 49 |
+
return () => timers.forEach(clearTimeout);
|
| 50 |
+
}, []);
|
| 51 |
+
|
| 52 |
+
useEffect(() => {
|
| 53 |
+
const id = setInterval(() => {
|
| 54 |
+
setProgress((prev) => {
|
| 55 |
+
if (prev >= 92) return prev;
|
| 56 |
+
const delta = prev < 60 ? 1.5 : prev < 80 ? 0.7 : 0.25;
|
| 57 |
+
return Math.min(92, prev + delta);
|
| 58 |
+
});
|
| 59 |
+
}, 250);
|
| 60 |
+
return () => clearInterval(id);
|
| 61 |
+
}, []);
|
| 62 |
+
|
| 63 |
+
return (
|
| 64 |
+
<div className="mx-auto mt-4 max-w-2xl rounded-2xl border-3 border-black bg-white p-6 sm:p-8 shadow-[5px_5px_0px_rgba(0,0,0,1)]">
|
| 65 |
+
<div className="flex items-center gap-4">
|
| 66 |
+
<div className="relative flex h-16 w-16 shrink-0 items-center justify-center rounded-xl border-2 border-black bg-[#FFD21E] shadow-[2px_2px_0px_rgba(0,0,0,1)] text-black">
|
| 67 |
+
<BrainCircuit size={32} strokeWidth={2.5} />
|
| 68 |
+
<span className="absolute -bottom-1.5 -right-1.5 flex h-6 w-6 items-center justify-center rounded-full border-2 border-black bg-[#0EA5A4]">
|
| 69 |
+
<span className="h-2.5 w-2.5 animate-spin rounded-full border-2 border-white border-t-transparent" />
|
| 70 |
+
</span>
|
| 71 |
+
</div>
|
| 72 |
+
<div className="min-w-0 flex-1">
|
| 73 |
+
<div className="text-xs font-black uppercase tracking-wider text-gray-500">
|
| 74 |
+
Generating dialogue
|
| 75 |
+
</div>
|
| 76 |
+
<h2 className="mt-1 truncate text-xl font-black text-black sm:text-2xl uppercase tracking-tight">{scenario}</h2>
|
| 77 |
+
</div>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
<div className="mt-6">
|
| 81 |
+
<div className="flex items-center justify-between text-xs font-black uppercase tracking-wider text-gray-600">
|
| 82 |
+
<span>{STAGES[stageIndex].label}</span>
|
| 83 |
+
<span>{Math.round(progress)}%</span>
|
| 84 |
+
</div>
|
| 85 |
+
<div className="mt-2 h-4 w-full overflow-hidden rounded-full border-2 border-black bg-gray-100">
|
| 86 |
+
<div
|
| 87 |
+
className="h-full rounded-full bg-[#0EA5A4] transition-[width] duration-300 ease-out"
|
| 88 |
+
style={{ width: `${progress}%` }}
|
| 89 |
+
/>
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<ul className="mt-8 space-y-3">
|
| 94 |
+
{STAGES.map((stage, index) => (
|
| 95 |
+
<StageItem
|
| 96 |
+
key={stage.label}
|
| 97 |
+
icon={stage.icon}
|
| 98 |
+
label={stage.label}
|
| 99 |
+
description={stage.description}
|
| 100 |
+
isDone={index < stageIndex}
|
| 101 |
+
isActive={index === stageIndex}
|
| 102 |
+
/>
|
| 103 |
+
))}
|
| 104 |
+
</ul>
|
| 105 |
+
|
| 106 |
+
<p className="mt-6 text-center text-xs font-black uppercase tracking-wider text-gray-400">
|
| 107 |
+
Hang tight — generating a custom dialogue can take up to 30 seconds.
|
| 108 |
+
</p>
|
| 109 |
+
</div>
|
| 110 |
+
);
|
| 111 |
+
}
|
app/(app)/practice/ScenarioCard.tsx
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import type { DialogueScenario } from "@/lib/dialogue-scenarios";
|
| 2 |
+
import { SCENARIO_THEMES } from "@/lib/dialogue-themes";
|
| 3 |
+
import { ArrowRight } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function ScenarioCard({
|
| 6 |
+
scenario,
|
| 7 |
+
index,
|
| 8 |
+
pending,
|
| 9 |
+
disabled,
|
| 10 |
+
onGenerate,
|
| 11 |
+
}: {
|
| 12 |
+
scenario: DialogueScenario;
|
| 13 |
+
index: number;
|
| 14 |
+
pending: boolean;
|
| 15 |
+
disabled: boolean;
|
| 16 |
+
onGenerate: () => void;
|
| 17 |
+
}) {
|
| 18 |
+
const theme = SCENARIO_THEMES[index % SCENARIO_THEMES.length];
|
| 19 |
+
const Icon = theme.icon;
|
| 20 |
+
|
| 21 |
+
return (
|
| 22 |
+
<button
|
| 23 |
+
onClick={onGenerate}
|
| 24 |
+
disabled={disabled}
|
| 25 |
+
className="group relative flex min-h-[180px] w-full flex-col text-left rounded-2xl bg-white p-6 border-3 border-black shadow-[5px_5px_0px_rgba(0,0,0,1)] hover:translate-x-[-2px] hover:translate-y-[-2px] hover:shadow-[7px_7px_0px_rgba(0,0,0,1)] active:translate-x-[2px] active:translate-y-[2px] active:shadow-[1px_1px_0px_rgba(0,0,0,1)] transition-all duration-200 disabled:opacity-60 disabled:pointer-events-none justify-between overflow-hidden"
|
| 26 |
+
>
|
| 27 |
+
<div>
|
| 28 |
+
<div className="flex items-center justify-between mb-4">
|
| 29 |
+
<div className={`flex h-12 w-12 shrink-0 items-center justify-center rounded-xl border-2 border-black ${theme.tile} ${theme.tileText} shadow-[2px_2px_0px_rgba(0,0,0,1)]`}>
|
| 30 |
+
<Icon size={24} strokeWidth={2.5} />
|
| 31 |
+
</div>
|
| 32 |
+
<div className="flex h-9 w-9 items-center justify-center rounded-full border-2 border-black bg-[#FFD21E] text-black shadow-[2px_2px_0px_rgba(0,0,0,1)] group-hover:scale-105 transition-transform">
|
| 33 |
+
<ArrowRight size={18} strokeWidth={3} className="group-hover:translate-x-0.5 transition-transform" />
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
<h3 className="text-lg font-black text-black tracking-tight leading-snug mb-2 line-clamp-1 uppercase">{scenario.title}</h3>
|
| 37 |
+
<p className="line-clamp-2 text-xs font-bold leading-relaxed text-gray-700">{scenario.description}</p>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div className="pt-4 mt-4 border-t-2 border-black w-full flex items-center justify-between">
|
| 41 |
+
<span className="text-xs font-black tracking-wider text-black uppercase">
|
| 42 |
+
Generate fresh
|
| 43 |
+
</span>
|
| 44 |
+
{pending ? (
|
| 45 |
+
<span className="h-4 w-4 rounded-full border-2 border-black border-t-transparent animate-spin" />
|
| 46 |
+
) : null}
|
| 47 |
+
</div>
|
| 48 |
+
</button>
|
| 49 |
+
);
|
| 50 |
+
}
|
app/(app)/practice/ScenarioLibrary.tsx
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { DIALOGUE_SCENARIOS } from "@/lib/dialogue-scenarios";
|
| 2 |
+
import { ScenarioCard } from "./ScenarioCard";
|
| 3 |
+
|
| 4 |
+
export function ScenarioLibrary({
|
| 5 |
+
activeScenario,
|
| 6 |
+
onGenerate,
|
| 7 |
+
}: {
|
| 8 |
+
activeScenario: string | null;
|
| 9 |
+
onGenerate: (scenario: string, label: string) => void;
|
| 10 |
+
}) {
|
| 11 |
+
return (
|
| 12 |
+
<div className="space-y-6">
|
| 13 |
+
<div className="flex flex-col gap-2">
|
| 14 |
+
<span className="text-xs font-black uppercase tracking-wider text-black bg-[#E6FBFA] border-2 border-black px-2.5 py-1 rounded-md shadow-[1px_1px_0px_rgba(0,0,0,1)] inline-block w-fit">
|
| 15 |
+
Starter packs
|
| 16 |
+
</span>
|
| 17 |
+
<h2 className="text-2xl sm:text-3xl font-black tracking-tight text-black uppercase">Scenario Library</h2>
|
| 18 |
+
<div className="text-xs font-bold text-gray-500 uppercase tracking-widest">
|
| 19 |
+
{DIALOGUE_SCENARIOS.length} scenarios available
|
| 20 |
+
</div>
|
| 21 |
+
</div>
|
| 22 |
+
|
| 23 |
+
<div className="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
| 24 |
+
{DIALOGUE_SCENARIOS.map((scenario, index) => (
|
| 25 |
+
<ScenarioCard
|
| 26 |
+
key={scenario.title}
|
| 27 |
+
scenario={scenario}
|
| 28 |
+
index={index}
|
| 29 |
+
pending={activeScenario === scenario.title}
|
| 30 |
+
disabled={activeScenario !== null}
|
| 31 |
+
onGenerate={() => onGenerate(scenario.scenario, scenario.title)}
|
| 32 |
+
/>
|
| 33 |
+
))}
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
);
|
| 37 |
+
}
|
app/(app)/practice/StageItem.tsx
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { Check, type LucideIcon } from "lucide-react";
|
| 4 |
+
|
| 5 |
+
export function StageItem({
|
| 6 |
+
icon: Icon,
|
| 7 |
+
label,
|
| 8 |
+
description,
|
| 9 |
+
isDone,
|
| 10 |
+
isActive,
|
| 11 |
+
}: {
|
| 12 |
+
icon: LucideIcon;
|
| 13 |
+
label: string;
|
| 14 |
+
description: string;
|
| 15 |
+
isDone: boolean;
|
| 16 |
+
isActive: boolean;
|
| 17 |
+
}) {
|
| 18 |
+
return (
|
| 19 |
+
<li
|
| 20 |
+
className={`flex items-start gap-3 rounded-xl border-2 px-3.5 py-3 transition-all duration-200 ${
|
| 21 |
+
isActive
|
| 22 |
+
? "border-black bg-[#FFF7D6] shadow-[2px_2px_0px_rgba(0,0,0,1)] translate-x-[-1px] translate-y-[-1px]"
|
| 23 |
+
: isDone
|
| 24 |
+
? "border-[#0EA5A4] bg-[#E6FBFA] opacity-80"
|
| 25 |
+
: "border-gray-200 bg-gray-50 opacity-60"
|
| 26 |
+
}`}
|
| 27 |
+
>
|
| 28 |
+
<div
|
| 29 |
+
className={`flex h-9 w-9 shrink-0 items-center justify-center rounded-lg border-2 transition-colors ${
|
| 30 |
+
isActive
|
| 31 |
+
? "border-black bg-white text-black"
|
| 32 |
+
: isDone
|
| 33 |
+
? "border-[#0EA5A4] bg-[#0EA5A4] text-white"
|
| 34 |
+
: "border-gray-300 bg-white text-gray-400"
|
| 35 |
+
}`}
|
| 36 |
+
>
|
| 37 |
+
{isDone ? <Check size={20} strokeWidth={3} /> : <Icon size={20} strokeWidth={2.5} />}
|
| 38 |
+
</div>
|
| 39 |
+
<div className="min-w-0 flex-1">
|
| 40 |
+
<div
|
| 41 |
+
className={`text-base font-black ${
|
| 42 |
+
isActive ? "text-black" : isDone ? "text-[#0a6e6d]" : "text-gray-500"
|
| 43 |
+
}`}
|
| 44 |
+
>
|
| 45 |
+
{label}
|
| 46 |
+
{isActive && (
|
| 47 |
+
<span className="ml-2 inline-flex gap-1 align-middle">
|
| 48 |
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-black [animation-delay:-0.3s]" />
|
| 49 |
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-black [animation-delay:-0.15s]" />
|
| 50 |
+
<span className="h-1.5 w-1.5 animate-bounce rounded-full bg-black" />
|
| 51 |
+
</span>
|
| 52 |
+
)}
|
| 53 |
+
</div>
|
| 54 |
+
<div
|
| 55 |
+
className={`mt-1 text-xs font-bold leading-relaxed ${
|
| 56 |
+
isActive ? "text-gray-700" : isDone ? "text-[#0a6e6d]/80" : "text-gray-400"
|
| 57 |
+
}`}
|
| 58 |
+
>
|
| 59 |
+
{description}
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</li>
|
| 63 |
+
);
|
| 64 |
+
}
|
app/(app)/practice/[storyId]/StoryLoader.tsx
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import Link from "next/link";
|
| 2 |
+
import { ArrowLeft } from "lucide-react";
|
| 3 |
+
|
| 4 |
+
export function StoryLoader() {
|
| 5 |
+
return (
|
| 6 |
+
<div className="duo-card mx-auto max-w-md p-8 text-center">
|
| 7 |
+
<p className="text-lg font-black text-[#3C3C3C]">Story no longer available</p>
|
| 8 |
+
<p className="mt-2 text-sm font-bold leading-6 text-[#777777]">
|
| 9 |
+
Generated stories are not stored or kept as a history in PraxaLing.
|
| 10 |
+
</p>
|
| 11 |
+
<Link href="/practice" className="mt-4 inline-flex items-center gap-2 text-sm font-black text-[#3B82F6]">
|
| 12 |
+
<ArrowLeft size={16} />
|
| 13 |
+
Back to practice
|
| 14 |
+
</Link>
|
| 15 |
+
</div>
|
| 16 |
+
);
|
| 17 |
+
}
|
app/(app)/practice/[storyId]/StoryQuiz.tsx
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import { motion } from "framer-motion";
|
| 5 |
+
import { BookOpen, CheckCircle } from "lucide-react";
|
| 6 |
+
|
| 7 |
+
type StoryQuestion = {
|
| 8 |
+
question: string;
|
| 9 |
+
options: string[];
|
| 10 |
+
correctIndex: number;
|
| 11 |
+
explanation: string;
|
| 12 |
+
};
|
| 13 |
+
|
| 14 |
+
const EASE = [0.16, 1, 0.3, 1] as const;
|
| 15 |
+
|
| 16 |
+
export function StoryQuiz({ storyId, content }: { storyId: string; content: string }) {
|
| 17 |
+
const [loading, setLoading] = useState(false);
|
| 18 |
+
const [questions, setQuestions] = useState<StoryQuestion[] | null>(null);
|
| 19 |
+
const [answers, setAnswers] = useState<Record<number, number>>({});
|
| 20 |
+
const [error, setError] = useState<string | null>(null);
|
| 21 |
+
|
| 22 |
+
const answeredAll = questions !== null && questions.length > 0 && Object.keys(answers).length === questions.length;
|
| 23 |
+
const score = answeredAll ? questions.filter((q, i) => answers[i] === q.correctIndex).length : null;
|
| 24 |
+
|
| 25 |
+
async function loadQuiz() {
|
| 26 |
+
setLoading(true);
|
| 27 |
+
setError(null);
|
| 28 |
+
try {
|
| 29 |
+
const res = await fetch(`/api/stories/${storyId}/questions`, {
|
| 30 |
+
method: "POST",
|
| 31 |
+
headers: { "content-type": "application/json" },
|
| 32 |
+
body: JSON.stringify({ content }),
|
| 33 |
+
});
|
| 34 |
+
if (!res.ok) throw new Error("failed");
|
| 35 |
+
const data = await res.json();
|
| 36 |
+
setQuestions(Array.isArray(data) ? data : []);
|
| 37 |
+
} catch {
|
| 38 |
+
setError("Could not load questions. Try again.");
|
| 39 |
+
} finally {
|
| 40 |
+
setLoading(false);
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
function handleAnswer(qIdx: number, oIdx: number) {
|
| 45 |
+
setAnswers((prev) => (prev[qIdx] !== undefined ? prev : { ...prev, [qIdx]: oIdx }));
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<section className="duo-card p-5 sm:p-6">
|
| 50 |
+
<div className="mb-5 flex items-center gap-3">
|
| 51 |
+
<div className="grid h-11 w-11 place-items-center rounded-2xl bg-[#EFF6FF] text-[#3B82F6]">
|
| 52 |
+
<BookOpen size={22} />
|
| 53 |
+
</div>
|
| 54 |
+
<div>
|
| 55 |
+
<h2 className="text-lg font-black text-[#3C3C3C]">Test yourself</h2>
|
| 56 |
+
<div className="text-xs font-bold text-[#777777]">Answer every question to finish the lesson.</div>
|
| 57 |
+
</div>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
{!questions && !loading && !error ? (
|
| 61 |
+
<button onClick={loadQuiz} className="btn-duo btn-duo-primary w-full sm:w-auto">
|
| 62 |
+
Generate questions
|
| 63 |
+
</button>
|
| 64 |
+
) : null}
|
| 65 |
+
|
| 66 |
+
{loading ? <p className="text-sm font-bold text-[#777777]">Generating questions...</p> : null}
|
| 67 |
+
|
| 68 |
+
{error ? (
|
| 69 |
+
<div className="space-y-3">
|
| 70 |
+
<p className="text-sm font-bold text-[#BE123C]">{error}</p>
|
| 71 |
+
<button onClick={loadQuiz} className="btn-duo btn-duo-white text-sm">
|
| 72 |
+
Retry
|
| 73 |
+
</button>
|
| 74 |
+
</div>
|
| 75 |
+
) : null}
|
| 76 |
+
|
| 77 |
+
{questions && questions.length === 0 ? (
|
| 78 |
+
<p className="text-sm font-bold text-[#777777]">No questions were generated for this story.</p>
|
| 79 |
+
) : null}
|
| 80 |
+
|
| 81 |
+
{questions && questions.length > 0 ? (
|
| 82 |
+
<div className="space-y-7">
|
| 83 |
+
{questions.map((q, questionIndex) => (
|
| 84 |
+
<QuizQuestion
|
| 85 |
+
key={questionIndex}
|
| 86 |
+
question={q}
|
| 87 |
+
index={questionIndex}
|
| 88 |
+
answer={answers[questionIndex]}
|
| 89 |
+
onAnswer={(optionIndex) => handleAnswer(questionIndex, optionIndex)}
|
| 90 |
+
/>
|
| 91 |
+
))}
|
| 92 |
+
|
| 93 |
+
{score !== null ? <QuizScore score={score} total={questions.length} /> : null}
|
| 94 |
+
</div>
|
| 95 |
+
) : null}
|
| 96 |
+
</section>
|
| 97 |
+
);
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
function QuizQuestion({
|
| 101 |
+
question,
|
| 102 |
+
index,
|
| 103 |
+
answer,
|
| 104 |
+
onAnswer,
|
| 105 |
+
}: {
|
| 106 |
+
question: StoryQuestion;
|
| 107 |
+
index: number;
|
| 108 |
+
answer: number | undefined;
|
| 109 |
+
onAnswer: (optionIndex: number) => void;
|
| 110 |
+
}) {
|
| 111 |
+
const made = answer !== undefined;
|
| 112 |
+
|
| 113 |
+
return (
|
| 114 |
+
<motion.div
|
| 115 |
+
initial={{ opacity: 0, y: 12 }}
|
| 116 |
+
animate={{ opacity: 1, y: 0 }}
|
| 117 |
+
transition={{ duration: 0.45, delay: index * 0.05, ease: EASE }}
|
| 118 |
+
className="space-y-3"
|
| 119 |
+
>
|
| 120 |
+
<p className="text-base font-black text-[#3C3C3C]">
|
| 121 |
+
{index + 1}. {question.question}
|
| 122 |
+
</p>
|
| 123 |
+
<div className="grid gap-3">
|
| 124 |
+
{question.options.map((option, optionIndex) => {
|
| 125 |
+
const chosen = answer === optionIndex;
|
| 126 |
+
const correct = optionIndex === question.correctIndex;
|
| 127 |
+
let className = "duo-card duo-card-interactive p-4 text-left text-sm font-black text-black";
|
| 128 |
+
|
| 129 |
+
if (made && correct) className = "rounded-2xl border-3 border-black bg-[#0EA5A4] p-4 text-left text-sm font-black text-white shadow-[3px_3px_0px_rgba(0,0,0,1)]";
|
| 130 |
+
if (made && chosen && !correct) className = "rounded-2xl border-3 border-black bg-[#FF8080] p-4 text-left text-sm font-black text-black shadow-[3px_3px_0px_rgba(0,0,0,1)]";
|
| 131 |
+
if (made && !chosen && !correct) className = "rounded-2xl border-2 border-gray-300 bg-gray-50 p-4 text-left text-sm font-black text-gray-400 shadow-[2px_2px_0px_rgba(0,0,0,0.15)]";
|
| 132 |
+
|
| 133 |
+
return (
|
| 134 |
+
<button key={optionIndex} disabled={made} onClick={() => onAnswer(optionIndex)} className={className}>
|
| 135 |
+
{option}
|
| 136 |
+
</button>
|
| 137 |
+
);
|
| 138 |
+
})}
|
| 139 |
+
</div>
|
| 140 |
+
{made ? <p className="text-xs font-bold leading-5 text-[#777777]">{question.explanation}</p> : null}
|
| 141 |
+
</motion.div>
|
| 142 |
+
);
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
function QuizScore({ score, total }: { score: number; total: number }) {
|
| 146 |
+
return (
|
| 147 |
+
<motion.div
|
| 148 |
+
initial={{ opacity: 0, scale: 0.96 }}
|
| 149 |
+
animate={{ opacity: 1, scale: 1 }}
|
| 150 |
+
transition={{ duration: 0.45, ease: EASE }}
|
| 151 |
+
className="rounded-2xl border-3 border-black bg-[#E6FBFA] p-6 text-center shadow-[5px_5px_0px_rgba(0,0,0,1)]"
|
| 152 |
+
>
|
| 153 |
+
<CheckCircle size={36} strokeWidth={2.5} className="mx-auto text-[#0B7C7B]" />
|
| 154 |
+
<div className="mt-2 text-3xl font-black text-[#0B7C7B]">
|
| 155 |
+
{score} / {total}
|
| 156 |
+
</div>
|
| 157 |
+
<p className="mt-1 text-sm font-black uppercase tracking-wide text-[#0B7C7B]">
|
| 158 |
+
{score === total ? "Perfect score!" : "Keep reading to improve!"}
|
| 159 |
+
</p>
|
| 160 |
+
</motion.div>
|
| 161 |
+
);
|
| 162 |
+
}
|
app/(app)/practice/[storyId]/StoryReader.tsx
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
import { useEffect, useMemo, useRef, useState } from "react";
|
| 4 |
+
import { StoryText } from "./StoryText";
|
| 5 |
+
import { WordPopup } from "./WordPopup";
|
| 6 |
+
import { VocabList } from "./VocabList";
|
| 7 |
+
import { StoryQuiz } from "./StoryQuiz";
|
| 8 |
+
|
| 9 |
+
type Vocab = { word: string; gloss: string };
|
| 10 |
+
|
| 11 |
+
type Props = {
|
| 12 |
+
storyId: string;
|
| 13 |
+
content: string;
|
| 14 |
+
vocab: Vocab[];
|
| 15 |
+
fromLang: string;
|
| 16 |
+
toLang: string;
|
| 17 |
+
};
|
| 18 |
+
|
| 19 |
+
type Selection = { word: string; rect: DOMRect } | null;
|
| 20 |
+
|
| 21 |
+
export function StoryReader({ storyId, content, vocab, fromLang, toLang }: Props) {
|
| 22 |
+
const [selection, setSelection] = useState<Selection>(null);
|
| 23 |
+
const [translation, setTranslation] = useState<string | null>(null);
|
| 24 |
+
const [translating, setTranslating] = useState(false);
|
| 25 |
+
// Increments per lookup so a slow response can't overwrite a newer word's translation.
|
| 26 |
+
const lookupSeq = useRef(0);
|
| 27 |
+
|
| 28 |
+
const vocabMap = useMemo(() => {
|
| 29 |
+
const map = new Map<string, string>();
|
| 30 |
+
for (const v of vocab) map.set(v.word.toLowerCase(), v.gloss);
|
| 31 |
+
return map;
|
| 32 |
+
}, [vocab]);
|
| 33 |
+
|
| 34 |
+
useEffect(() => {
|
| 35 |
+
fetch(`/api/stories/${storyId}/read`, { method: "POST" }).catch(() => {});
|
| 36 |
+
}, [storyId]);
|
| 37 |
+
|
| 38 |
+
async function handleWord(word: string, rect: DOMRect) {
|
| 39 |
+
const clean = word.replace(/[^\p{L}\p{M}\p{N}\-']+/gu, "");
|
| 40 |
+
if (!clean) return;
|
| 41 |
+
const seq = ++lookupSeq.current;
|
| 42 |
+
setSelection({ word: clean, rect });
|
| 43 |
+
setTranslation(null);
|
| 44 |
+
|
| 45 |
+
const pre = vocabMap.get(clean.toLowerCase());
|
| 46 |
+
if (pre) {
|
| 47 |
+
setTranslation(pre);
|
| 48 |
+
return;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
setTranslating(true);
|
| 52 |
+
try {
|
| 53 |
+
const res = await fetch("/api/translate", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
headers: { "content-type": "application/json" },
|
| 56 |
+
body: JSON.stringify({ text: clean, from: fromLang, to: toLang }),
|
| 57 |
+
});
|
| 58 |
+
const data = await res.json();
|
| 59 |
+
if (seq === lookupSeq.current) setTranslation(data.translation ?? data.error ?? "-");
|
| 60 |
+
} catch {
|
| 61 |
+
if (seq === lookupSeq.current) setTranslation("-");
|
| 62 |
+
} finally {
|
| 63 |
+
if (seq === lookupSeq.current) setTranslating(false);
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
return (
|
| 68 |
+
<div className="relative space-y-6">
|
| 69 |
+
<StoryText content={content} onWord={(word, rect) => void handleWord(word, rect)} />
|
| 70 |
+
|
| 71 |
+
{selection ? (
|
| 72 |
+
<WordPopup
|
| 73 |
+
word={selection.word}
|
| 74 |
+
rect={selection.rect}
|
| 75 |
+
translation={translation}
|
| 76 |
+
translating={translating}
|
| 77 |
+
onClose={() => setSelection(null)}
|
| 78 |
+
/>
|
| 79 |
+
) : null}
|
| 80 |
+
|
| 81 |
+
<VocabList vocab={vocab} />
|
| 82 |
+
|
| 83 |
+
<StoryQuiz storyId={storyId} content={content} />
|
| 84 |
+
</div>
|
| 85 |
+
);
|
| 86 |
+
}
|
app/(app)/practice/[storyId]/StoryText.tsx
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
|
| 3 |
+
type Props = {
|
| 4 |
+
content: string;
|
| 5 |
+
onWord: (word: string, rect: DOMRect) => void;
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
export function StoryText({ content, onWord }: Props) {
|
| 9 |
+
return (
|
| 10 |
+
<section className="duo-card p-5 sm:p-7">
|
| 11 |
+
{content.split(/\n\n+/).map((paragraph, paragraphIndex) => (
|
| 12 |
+
<p key={paragraphIndex} className="mb-5 text-lg font-bold leading-8 text-[#3C3C3C]">
|
| 13 |
+
{tokenize(paragraph).map((token, tokenIndex) =>
|
| 14 |
+
token.word ? (
|
| 15 |
+
<span
|
| 16 |
+
key={tokenIndex}
|
| 17 |
+
className="cursor-pointer rounded-md px-0.5 underline decoration-[#0EA5A4] decoration-2 underline-offset-4 transition hover:bg-[#CCFBF1]"
|
| 18 |
+
onClick={(e) => {
|
| 19 |
+
const rect = (e.target as HTMLElement).getBoundingClientRect();
|
| 20 |
+
onWord(token.text, rect);
|
| 21 |
+
}}
|
| 22 |
+
>
|
| 23 |
+
{token.text}
|
| 24 |
+
</span>
|
| 25 |
+
) : (
|
| 26 |
+
<span key={tokenIndex}>{token.text}</span>
|
| 27 |
+
),
|
| 28 |
+
)}
|
| 29 |
+
</p>
|
| 30 |
+
))}
|
| 31 |
+
</section>
|
| 32 |
+
);
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
function tokenize(text: string): Array<{ text: string; word: boolean }> {
|
| 36 |
+
const out: Array<{ text: string; word: boolean }> = [];
|
| 37 |
+
const re = /([\p{L}\p{M}][\p{L}\p{M}\p{N}\-']*)|([^\p{L}\p{M}\p{N}]+)/gu;
|
| 38 |
+
for (const match of text.matchAll(re)) {
|
| 39 |
+
if (match[1]) out.push({ text: match[1], word: true });
|
| 40 |
+
else if (match[2]) out.push({ text: match[2], word: false });
|
| 41 |
+
}
|
| 42 |
+
return out;
|
| 43 |
+
}
|
app/(app)/practice/[storyId]/VocabList.tsx
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
type Vocab = { word: string; gloss: string };
|
| 2 |
+
|
| 3 |
+
export function VocabList({ vocab }: { vocab: Vocab[] }) {
|
| 4 |
+
if (vocab.length === 0) return null;
|
| 5 |
+
return (
|
| 6 |
+
<section className="duo-card p-5">
|
| 7 |
+
<h2 className="duo-eyebrow">Key vocabulary</h2>
|
| 8 |
+
<dl className="mt-4 grid gap-3 sm:grid-cols-2">
|
| 9 |
+
{vocab.map((v) => (
|
| 10 |
+
<div key={v.word} className="duo-soft-panel p-4">
|
| 11 |
+
<dt className="text-base font-black text-[#3C3C3C]">{v.word}</dt>
|
| 12 |
+
<dd className="mt-1 text-sm font-bold text-[#777777]">{v.gloss}</dd>
|
| 13 |
+
</div>
|
| 14 |
+
))}
|
| 15 |
+
</dl>
|
| 16 |
+
</section>
|
| 17 |
+
);
|
| 18 |
+
}
|